Think of the first scenario in our chapter on “Stopping Threads”. Our thread expects a value to be updated by some method and this value is checked everytime our thread is going to run inside a loop. Lets say we have implemented synchronization for our run method, we will end up with another new problem.
How can some other thread update a value that our run method is accessing? Since the method is synchronized, all the objects it is going to access are exclusively locked by our thread and so, the other thread would have to wait to update this variable. Unfortunately our thread has locked this variable so the other thread can never update this variable and hence our thread will never terminate.
Before we get into the solution part of the problem, first we must understand what the scope of a lock is.
Scope of a Lock
The scope of a lock is defined as the period of time between when the lock is grabbed and released. In our examples so far, we have used only synchronized methods; this means that the scope of these locks is the period of time it takes to execute the methods. This is referred to as method scope.
The problem at this point relates to the scope of the lock: the scope of the run() method is too large. By synchronizing the run() method, the lock is grabbed and never released.
The setDone() method performs only one operation with the done flag: it stores a value into the flag. The run() method also performs one operation with the done flag: it reads the value during each iteration of the loop. Furthermore, it does not matter if the value changes during the iteration of these methods, as each loop must complete anyway.
The issue here is that we potentially have a race condition because one piece of data is being shared between two different threads. In our first example, the race condition came about because the threads were accessing multiple pieces of data and there was no way to update all of them atomically without using the synchronized keyword. When only a single piece of data is involved, there is a different solution.
Java specifies that basic loading and storing of variables (except for long and double variables) is atomic. That means the value of the variable can't be found in an interim state during the store, nor can it be changed in the middle of loading the variable to a register. The setDone() method has only one store operation; therefore, it is atomic. The run( ) method has only one read operation. Since the rest of the run() method does not depend on the value of the variable remaining constant, the race condition should not exist in this case.
Unfortunately, Java's memory model is a bit more complex. Threads are allowed to hold the values of variables in local memory (e.g., in a machine register). In that case, when one thread changes the value of the variable, another thread may not see the changed variable. This is particularly true in loops that are controlled by a variable (like the done flag that we are using to terminate the thread): the looping thread may have already loaded the value of the variable into a register and does not necessarily notice when another thread changes the variable.
One way to solve this problem is to provide setter and getter methods for the variable. We can then simply synchronize access by using the synchronized keyword on these methods. This works because acquiring a synchronization lock means that all temporary values stored in registers are flushed to main memory. However, Java provides a more elegant solution: the volatile keyword. If a variable is marked as volatile, every time the variable is used it must be read from main memory. Similarly, every time the variable is written, the value must be stored in main memory. Since these operations are atomic, we can avoid the race condition in our example by marking our done flag as volatile.
So why is volatile necessary? Volatile variables solve only the problem introduced by Java's memory model. They can be used only when the operations that use the variable are atomic; meaning the methods that access the variable must use only a single load or store. If the method has other code, that code may not depend on the variable changing its value during its operation.
As we mentioned, we could have solved this problem by using synchronized setter and getter methods to access the variable. However, that would be fairly complex. We must invoke another method, including setting up parameters and the return variable. We must grab and release the lock necessary to invoke the method. And all for a single line of code, with one atomic operation, that is called many times within a loop. The concept of using a done flag is common enough that we can make a very strong case for the volatile keyword.
Previous: The Synchronized Keyword