I would suggest that a trader should only offer its specialty in swaps. For example, if the wheat trader needs barley, it may attempt to swap wheat for barley, but not rice. This is the way I wrote my solution, it seems to work, and it's relatively simple. If you like, you can experiment with a more aggressive strategy and see if you get better results, where “better” means that more stuff gets delivered to brewers.
There's also a question of how much to swap and when. For example, suppose the wheat trader has 6 bushels of wheat but no barley and receives a get order for 4 wheat and 4 barley. Should it try to swap wheat for barley, and if so, how much? Some possibilities are
There are two command-line options that can help with debugging. You can invoke the program as
java Project2 -v 3 100The -v means “verbose”. It will print lots of messages about significant events in the Supplier and Brewer threads. You can add your own debugging output with calls to
Project2.debug(message)The message will only be printed if -v was specified. Each message will be preceded by an indication of which thread was running when it was printed.
You may also supply the -r flag to make the output less random. Various choices in the program, such as the time between Supplier cycles, the amount a grain to produce on each cycle, and the trader contacted by a brewer, are controlled by a pseudo random number generator. Each time you run your program, the generator starts with a different “seed”, so your results will be different each run, even if you don't change anything in your program. If you specify -r, the same seed is used each time, so there will be much less variation. You still may see a slight variation due to random timing effects, but it should be much less.
See also Q3.
When the main program has created and started the supplier and all the brewer threads, it waits for the supplier to terminate, which happens when it has completed the specified number of rounds of production. The main program then sleeps for three seconds (giving the system a chance to settle down), interrupts all the brewer threads, and finally gathers and prints information about the state of the system. The auditing code calls Supplier.getProduction to determine the total amount of each grain produced, Brewer.getConsumption to find out how much was consumed, and Trader.getAmountOnHand to see how much was left over. If all is well, the amount produced of each grain should exactly equal the sum of the amount consumed and the amount left on hand. The difference between these quantities is printed near the end of the output. If it isn't zero, you should investigate.
Another feature should be helpful in tracking down deadlocks. Before printing out the consumption statistics, the main program interrupts each brewer thread by calling Thread.interrupt(). Most likely, the thread will be blocked on a wait() call. When it gets interrupted, it will throw an InterruptedException. The main loop in Brewer.run()() catches InterruptedException, prints a message, and returns. Your code should not try to catch InterruptedException, if it does, your your program may just hang, since the main method does a join on each of the brewer threads before exiting.
Two problems can occur here -
The Trader maintains a field called surplus, which records the difference between the amount it has on hand of its specialty grain and the amount it has earmarked for brewers. If this quantity is positive, the trader may give some or all of it to other brewers or traders. Otherwise, it will refuse. Note that the “surplus” may be negative, indicating that the trader is over-committed.
Here's your scenario with this technique added:
5. We call the barley trader offering to swap 6 bushels of wheat
for barley. Before the call, we subtract 6 from the surplus giving 1. We
may again block, but as before, if the program terminates while we are
blocked, everything will balance.
6. We get back 6 bushels of barley and update our state to
[4W, 8B, 7R, 0C]
7. We now have enough to satisfy the brewer's request. We subtract
it from our current amount on hand, yielding [1W, 0B, 1R, 0C].
8. The brewer's call to get() returns, and it adds [3W, 8B, 6R, 0C]
to its record of total consumption.
1. a brewer calls get [3W, 8B, 6R, 0C] - say of the wheat trader, who has
[5W, 2B, 7R, 0C]
Let's assume this is the first brewer to call, so the wheat surplus is 5 before
the call.
2. we decrement wheat by 3 because we have enough.
We decrement surplus by three, giving 2.
3. we then see that we don't have enough barley, nor do we have
enough extra wheat to swap for it, so we call a waitForSupplier
function that essentially waits until the supplier has given us enough
to swap.
Since surplus is 2, we could swap two wheat for barley, or we may just decide
to wait because we need 6 bushels, not 2. Let's assume we wait.
Two problems can occur here -
If the program terminates at this point, you will still report that you have
5 wheat, so there's no imbalance.
Nobody can grab the wheat grain because the surplus is zero. Other
brewers could grab other grains if you have enough to completely satisfy
their requests. It's not clear to me that that would be a problem (it may
hurt fairness but improve overall performance). If you want to prevent it,
you could keep track of the surplus in each grain.
To complete the scenario above,
4. The supplier gives us 5 bushels of wheat. We now have [10W, 2B,
7U] and our surplus is 7.
If the program happens to terminate between steps 7 and 8 (or between 5 and 6)
there will still be a deficit. However, that's very unlikely to happen. The
supplier stops producing for a while before terminating the program so all
brewer threads are almost certainly blocked waiting for something, not moving
between steps.
Suppose tw = P2.specialist(Grain.WHEAT) and tb = P2.specialist(Grain.BARLEY).
In tw.get():
... // wait until I have 2 bushels of wheat to swap onHand.change(Grain.WHEAT, -2) // Load two bushels of wheat onto the truck tb.swap(Grain.WHEAT, 2); // ... and ship it to the barley trader onHand.change(Grain.BARLEY, 2);// Unload two bushels of barley from the truckand in tb.swap(Grain.WHEAT, 2):
... // wait until I have 2 bushels of barley onHand.change(Grain.WHEAT, 2) // Unload two bushels of wheat from the truck onHand.change(Grain.BARLEY, -2) // ... and replace it with two bushels of barley return;[Note: In the actual code, the calls to onHand.change() would be inside some syncronized method rather than directly inside the unsynchronized method get() as this simplified code would imply.]
When trader tb does onHand.change(Grain.BARLEY, -2), the system will be briefly out of balance because two bushels of barley have “disappeared”, but immediately after the procedure returns, tw will do onHand.change(Grain.BARLEY, 2), restoring the balance. However, when trader tw does onHand.change(Grain.WHEAT, -2), the missing wheat will not be restored until trader tb finishes the operation labeled “// wait until I have 2 bushels of barley”. This delay could be arbitrarily long, and the the supplier may decide to terminate the program during this period.
The problem in this case could be fixed either by moving onHand.change(Grain.WHEAT, -2) after the swap() call in tw or by moving onHand.change(Grain.WHEAT, 2) before the wait in trader tb.
This modification is not enough to prevent the kind of “deadlock” you mention, in all cases. Consider the following scenario: Each trader has one unit of its specialty and nothing else. Each receives a request [1W, 1B, 1R, 1C] from a brewer. From a “God's eye” view it is clear that one of the requests could be satisfied, but it is not at all clear (at least to me) how to program the system to find such a solution. In my solution, each trader would have a surplus of zero and thus refuse to swap for the other missing grains. Suppose I allowed a trader to swap even though its stock of its specialty has already been requested by brewers. The wheat trader might try to swap wheat for barley just as the barley trader is trying to swap barley for barley and the barley trader is trying to swap barley for wheat. Once again: deadlock! I have no doubt that a solution that avoid this problem is possible (it would require some sort of symmetry breaking), but it would be far beyond the scope of this project.