In the previous chapter, we covered a lot of ground: we examined how to create and start threads, how to arrange for them to terminate, how to name them, how to monitor their life cycles, and so on. In the examples of that chapter, however, the threads that we examined were more or less independent: they did not need to share any data between them.
In this chapter, we look at the issue of sharing data between threads. Sharing data between threads is often hampered due to what is known as a race condition between the threads attempting to access the same data more or less simultaneously. In this chapter, we’ll look at the concept of a race condition as well as examining a mechanism that solves race conditions. We will see how this mechanism can be used not only to coordinate access to data, but also for many problems in which synchronization is needed between threads. Before we start, let’s introduce a few concepts.
As an application designer for a major bank, we are assigned to the development team for the automated teller machine (ATM). As our first assignment, we are given the task of designing and implementing the routine that allows a user to withdraw cash from the ATM. A first and simple attempt at an algorithm may be as follows (see Figure 3.1 for the flow chart):
Check to make sure that the user has enough cash in the bank account to allow the withdrawal to occur. If the user does not, then go to step 4.
Subtract the amount ...