Threads may be seen as methods that execute at "the same time" as other methods. Normally, we think sequentially when writing a computer program. From this perspective, only one thing executes at a time. However, with today's multi-core processors, it is possible to literally have several things going on at the very same time while sharing the same memory. There are lots of ways that this is done in the real world, and this chapter goes over them in a way that you can apply to your own projects.
14.6 CASE STUDY: Cooperating Threads
The Bakery Class
Finally, Bakery is the simplest class to design. It contains the main() method, which gets the whole simulation started. As we said, its role will be to create one Clerk thread and several Customer threads, and get them all
started (Fig. 14.24). Notice that the Customers and the Clerk
are each passed a reference to the shared TakeANumber gadget.
Problem: Nonexistent Customers
Now that we have designed and implemented the classes, let’s run several experiments to test that everything works as intended. Except for the synchronized nextNumber() method, we’ve made little attempt to make sure that the Customer and
Clerk threads will work together cooperatively, without violating the real-world constraints that should be satisfied by the simulation. If we run the simulation as it is presently
coded, it will generate five customers and the clerk will serve all of them. But we get something like the following output:
Our current solution violates an important real-world constraint: You can’t serve customers before they enter the line! How can we ensure that the clerk doesn’t serve a customer unless there’s actually a customer waiting?
The wrong way to address this issue would be to increase the amount of sleeping that the Clerk does between serving customers. Indeed, this would allow more customer threads to run, so it might appear to have the desired effect, but it doesn’t
truly address the main problem: A clerk cannot serve a customer if no customer is waiting.
The correct way to solve this problem is to have the clerk check that there are customers waiting before taking the next customer. One way to model this would be to add a customerWaiting() method to our
TakeANumber object. This method would return true whenever next
is greater than serving. That will correspond to the real-world situation in which the clerk can see customers waiting in line. We can make the following modification to Clerk.run():
And we add the following method to TakeANumber (Fig. 14.25):
In other words, the Clerk won’t serve a customer unless there are customers waiting—that is, unless next is greater than serving. Given
these changes, we get the following type of output when we run the
simulation:
This example illustrates that when application design involves cooperating threads, the algorithm used must ensure the proper cooperation and
coordination among the threads.