The answer to Q6 should read
The problem in this case could be fixed either by moving onHand[0] -= 2 after the swap call in broker 0, or by moving onHand[0] += 2 before the wait in broker 1.Note also that the changes to onHand need to be in a synchronized method called by get rather than directly in get as the example code would imply.
I would suggest that a broker should only request swaps in its specialty. For example, if the gold supplier needs uranium, it may attempt to swap gold for uranium, but not platinum. 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 consumers.
There's also a question of how much to swap and when. For example, suppose the gold supplier has 6 ounces of gold but no uranium and receives a get order for 4 gold and 4 uranium. Should it try to swap gold for uranium, 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 Refiner and Consumer 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 Refiner cycles, the amount a metal to produce on each cycle, and the broker contacted by a consumer, are controlled by a random-number generator. Each time you run your program, the random-number 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 refiner has completed the specified number of rounds of production, it sleeps for three seconds and then gathers and prints information about the state of the system. It calls Supplier.getProduction to determine the total amount of each metal produced, Consumer.getConsumption to find out how much was consumed, and Broker.getAmountOnHand to see how much was left over. If all is well, the amount produced 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. After the program has printed out the consumption statistics, it interrupts each consumer 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. Each wait() in your program should be inside a try statement such as
try { wait(); } catch (InterruptedException e) { Project2.debug(... whatever ...); throw new StopThread(); }The output of the Project2.debug call should indicate why the thread is waiting, what it's waiting for, etc. Be sure to include the throw statement at the end of the catch clause. If you don't add this statement, your program will just hang, since the main method does a join on each of the consumer threads before exiting.
Two problems can occur here -
The Broker maintains a field called surplus, which records the difference between the amount it has on hand of its specialty metal and the amount it has earmarked for consumers. If this quantity is positive, the broker may give some or all of it to other consumers or brokers. Otherwise, it will refuse. Note that the "surplus" may be negative, indicating that the broker is over-committed.
Here's your scenario with this technique added:
1. a consumer calls get [3G, 8P, 6U] - say of the gold broker, who has
[5G, 2P, 7U]
Let's assume this is the first consumer to call, so the gold surplus is 5 before
the call.
2. we decrement gold by 3 because we have enough.
We decrement surplus by three, giving 2.
3. we then see that we don't have enough platinum, nor do we have
enough extra gold to swap for it, so we call a waitForRefiner
function that essentially waits until the refiner has given us enough
to swap.
Since surplus is 2, we could swap two gold for platinum, or we may just decide
to wait because we need 6 ounces, 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 gold, so there's no imbalance.
Nobody can grab the gold metal because the surplus is zero. Other
consumers could grab other metals 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 metal.
To complete the scenario above,
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
refiner stops producing for a while before terminating the program so all
consumer threads are almost certainly blocked waiting for something, not moving
between steps.
In broker[0].get():
... // wait until I have 2 ounces of gold to swap onHand[0] -= 2; // Load two ounces of gold onto the truck broker[1].swap(GOLD, 2); // ... and ship it to the other broker onHand[1] += 2; // Unload two ounces of platinum from the truckand in broker[1].swap():
... // wait until I have 2 ounces of platinum onHand[0] += 2; // Unload two ounces of gold from the truck onHand[1] -= 2; // ... and replace it with two ounces of platinum return;When broker 1 does onHand[1] -= 2 the system will be briefly out of balance because two ounces of platinum have "disappeared", but immediately after the procedure returns, broker[0] will do onHand[1] += 2, restoring the balance. However, when broker 0 does onHand[0] -= 2, the missing gold will not be restored until broker 1 finishes the operation labeled "// wait until I have 2 ounces of platinum". This delay could be arbitrarily long, and the the refiner may decide to terminate the program during this period.
The problem in this case could be fixed either by moving onHand[0] -= 2 after the wait in broker 0, or by moving onHand[0] += 2 before the wait in broker 1.
This modification is not enough to prevent the kind of "deadlock" you mention, in all cases. Consider the following scenario: Each broker has one unit of its specialty and nothing else. Each receives a request [1G, 1P, 1U] from a consumer. 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 broker would have a surplus of zero and thus refuse to swap for the other missing metals. Suppose I allowed a broker to swap even though its stock of its specialty has already been requested by consumers. The gold broker might try to swap gold for platinum just as the platinum broker is trying to swap platinum for uranium and the uranium broker is trying to swap uranium for gold. 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.