CS 537 Assignment 2 CS 537 Spring 2007 Assignment 2 Making Beer Due Wednesday, Feb 28 at 1:00 am. Note: There is a Frequently Asked Questions (FAQ) page for this project. It should be considered an integral part of the specifications. Check it from time to time, since new items may be added. Errata Feb 10 There are seven Java source files provided. An earlier version of these instructions only mentioned five of them and only six were installed in ~cs537-1/public/html/source/p2. Introduction For this assignment, you will write a trading system to supply Brewers with the four most common grains used in production of beer: corn, barley, rice, and wheat. The program will manage a single supplier and any number of brewers, as well as four traders who are responsible for stocking and delivering grain to brewers that request it. Each trader maintains a stock of all four types of grain, but is a “specialist” for only one of them, called its specialty. From time to time, the supplier delivers a shipment of grain to the trader that specializes in that type of grain. For example, the supplier might deliver 30 bushels of barley to the BARLEY trader, and later it might deliver 20 bushels of wheat to the WHEAT trader. Brewers periodically place purchase orders with Traders. Each order specifies a number of bushels of each type of grain. An order can be placed with any trader; in this program Brewers choose Traders at random. A Trader will fill the order from its own stock if possible. If the specialist for grain G cannot fill an order because it does not have enough G on hand, it just waits until it gets more from the Supplier. If it is short of some grain that is not its specialty, it attempts to get more by trading with the specialist for that grain. In any case, the trader does not respond to the brewer until it can fill its entire order. To keep things simple, we will assume, somewhat unrealistically, that bushels of corn, barley, wheat, and rice are all equally valuable. That is, three bushels of corn can be swapped for three bushels or wheat, three bushels of barley, etc. For example, suppose a brewer places an order with the WHEAT trader for for 4 bushels of wheat and 1 bushel of barley. If that trader has enough wheat and barley on hand, it will fill the order directly from its stocks. If it has fewer than 4 bushels of wheat on hand, it will wait for the supplier to deliver more. If it has lots of wheat but no barley, it will call the BARLEY trader and offer to swap wheat for bareley. A trader treats a request from another trader just like an order from a brewer. If it can fill it immediately, it will. Otherwise, since the request must be for its specialty grain, it will just wait until it gets more from the supplier. Implementing the System in Java We have supplied four classes, P2, Supplier, Brewer, and Order; one enumeration, Grain; and and one interface, Trader. The classes Supplier and Brewer are meant to be used as threads (that is, they implement Runnable). You are expected to write a class TraderImpl that implements Trader. TraderImpl is a “monitor” class, meaning that a Trader does not act on its own, only in response to a call from a Supplier or Brewer. P2 is the main class. It creates one Supplier thread and some number (specified on the command line) of Brewer threads and starts them running. From time to time the Supplier thread delivers a load of some grain to the specialist for that grain by calling the Trader method
public void deliver(int bushels);
From time to time a Brewer places an order by contacting a random Trader and calling the Trader method
public void get(Order order);
The order order contains one non-negative integer for each type of grain, indicating how much of that grain is desired. For example, to get three bushels of wheat and one bushel of barley from the BARLEY Trader, a Brewer might use code like this:
Order order = new Order();
order.set(Grain.WHEAT, 3);
order.set(Grain.BARLEY, 1);
P2.specialist(Grain.BARLEY).get(order);
As you can see, the class P2 provides several handy utilities as static methods. We will explain these in more detail below. A Trader who does not have enough of a grain on hand to satisfy a request may try to get more from the specialist in that grain by calling the method
public void swap(Grain what, int amt);
This method indicates that the calling Trader is willing to swap amt bushels of grain what for amt bushels of the supplier's specialty. For example, the call
P2 . specialist(Grain.WHEAT) . swap(CORN, 2);
indicates that the caller is willing to swap 2 bushels of corn for two bushels of wheat. After the call returns (calls to get and swap never fail, but they may delay the caller for a while), the caller should subtract 2 from its supply of corn and add 2 to its supply of wheat. When a Trader cannot satisfy a get or swap request because it does not have enough of its specialty grain, it blocks the caller (using the Java wait method) until it can. A Trader that is blocked waiting for something should continue to accept calls from the Suplier, Brewers, or other Traders. Your solution must be deadlock-free, but it does not have to be "fair". In particular, you do not have to prevent starvation (an unlucky brewer who repeatedly gets passed over). Details Grab copies of all seven .java files form ~cs537-1/public/html/source/p2:
mkdir project2
cd project2
cp ~cs537-1/public/html/source/p2/*.java .
Read the documenatation online. WARNING: These classes may need to be updated from time to time. Check the Frequently Asked Questions (FAQ) frequently to see if you need to fetch a fresh copy. GetOpt.java (documentation) This is a handy class for parsing command-line options. It is used by Project2.main() so you need copies of it, but you do not need to read or understand it (but you can if you like). P2.java (documentation) This is the main program. It accepts two command line arguments: the number of brewer and the number of iterations executed by the Supplier before it terminates. There are also two optional command-line flags -v Request verbose debugging (see setVerbose). -r Use the same random seed each time the program is run. This will cut down the amount of variability from run to run and may be helpful in reproducing errors. In addition to the main method, P2 provides several public static methods. The method debug(message) prints the message on System.out provided an internal verbose flag is on; the method setVebose(boolean) turns the verbose flag on or off (by default it is off). The debug method precedes the message with the name of thread that printed it. There is also a version of debug that accepts “printf” style arguments. The method specialist(Grain g) returns a reference to the Trader that specializes in a particular grain. Thus, to get a pointer to the WHEAT Trader, use the expression P2.specialist(Grain.WHEAT); There are a couple more static methods used by parts of the program that we wrote for you, so you don't need to understand them. See the online documentation for more details. Supplier.java (documentation) This Runnable class has a constructor and methods run and getProduction. The run method goes through a number of iterations specified in the constructor. On each iteration, it produces a random amount of a random grain and delivers it to the appropriate Trader by calling its deliver method. The main program creates a Supplier thread, starts it running, and waits for it to finish (by returning from run()). When the Supplier is done, the main program waits three seconds, kills off the Brewer threads by interrupting them, and prints some statistics. Each Brewer thread will see the interrupt by getting an InterruptedException. The method Brewer.run() catches this exception, prints a message, and returns. Your code should not catch InterruptedException. Any method that contains a call to a method that throws InterruptedException should simply declare that it throws InterruptedException in its header. The getProduction() method is used by P2.main to audit the global state when the system shuts down. Brewer.java (documentation) This class is also a simple Runnable. It iterates generating random orders and calling the get method of random Trader until it is interrupted by the main program. It also has a getConsumption method used by P2.main to audit the global state when the system shuts down. Trader.java (documentation) This is an interface. You must write a class called TraderImpl that implements this interface. (The name of your class must be spelled exactly this way.) Your class must implement the four methods specified in the interface, as well as a constructor that has one argument of type Grain. All four methods should be protected against race conditions, but you will probably find that they cannot all be synchronized methods. TraderImpl(Grain specialty) The constructor takes a single Grain parameter, the code for the specialty grain of this Trader. void get(Order order) A brewer calls this method to place an order. The argument indicates the number of bushels each type of grain required. It should return only when the order has been completely filled. void deliver(int amt) The Supplier calls this method to deliver a load of grain to the Trader. The argument amt is a number of bushels. The grain is this Trader's specialty. void swap(Grain what, int amt) Another Trader calls this method to swap one grain for another. The what argument indicates one of the grains; the other one is the grains in which this Trader specializes. The amt argument indicates how many bushels to swap. void Order getAmountOnHand() This method is used by P2.main to audit the global state when the system shuts down. The Trader should return an Order that indicates the total amount of each type of grain it currently has in stock. Grain.java (documentation) Order.java (documentation) Simple data structures used to support the methods described above. Hints Remember the cardinal rule of concurrent programming: Any variable that may be accessed by more than one process and modified by any of them should only be accessed inside a critical section. In the context of this project, that means that fields of class TraderImpl should only be accessed inside synchronized methods. On the other hand, there's another rule that will help you avoid deadlocks: A synchronized method should never call a synchronized method of another object. There are exceptions to this rule, but they are rare. In the context of this project, it means that TraderImpl.get cannot be synchronized because it needs to call TraderImpl.swap in another Trader. Instead, define your own private synchronized methods to read and/or update fields of TraderImpl and call them from TraderImpl.get. There will undoubtedly be more hints provided by your fellow students. Watch the Frequently Asked Questions (FAQ) for more information. What to turn in Turn all .java files you wrote or modified. Do not turn in .class files. No transcript is necessary. Grading Please carefully consider all the grading criteria discussed in class. Correctness will be 80% of your grade, and style will be 20% of your grade. 1 However, at first you should probably look only at the first 3 questions in the FAQ. The remaining questions involve "advanced" topics that will only confuse you if you're trying to get the basic functionality working. Last modified: Sat Feb 10 17:33:45 CST 2007 by Marvin Solomon