Threads and Concurrent Programming

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 

Annotation 2020-03-25 163415

coded, it will generate five customers and the clerk will serve all of them. But we get something like the following output:

Annotation 2020-03-25 163512

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 The in which the clerk can see customers waiting in line. We can make the following modification to

Annotation 2020-03-25 163809

And we add the following method to TakeANumber (Fig. 14.25): Annotation 2020-03-25 163953

Annotation 2020-03-25 163913

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:

Annotation 2020-03-25 164132

This example illustrates that when application design involves cooperating threads, the algorithm used must ensure the proper cooperation and coordination among the threads.

Annotation 2020-03-25 164219