Threads of Control |
The Java language and runtime system support thread synchronization through the use of monitors, which were first outlined in C. A. R. Hoare's article Monitors: An Operating System Structuring Concept (Communications of the ACM, 17(10), 549-557, 1974). A monitor is associated with a specific data item (a condition variable) and functions as a lock on that data. When a thread holds the monitor for some data item, other threads are locked out and cannot inspect or modify the data.The code segments within a program that access the same data from within separate, concurrent threads are known as critical sections. In the Java language, you mark critical sections in your program with the
synchronized
keyword.
Note: Generally, critical sections in Java programs are methods. You can mark smaller code segments as synchronized. However, this violates object-oriented paradigms and leads to confusing code that is difficult to debug and maintain. For the majority of your Java programming purposes, it's best to usesynchronized
only at the method level.
In the Java language, a unique monitor is associated with every object that has a synchronized method. The
CubbyHole
class for the producer/consumer example introduced in the previous section has two synchronized methods: theput
method, which is used to change the value in theCubbyHole
, and theget
method, which is used to retrieve the current value. Thus the system associates a unique monitor with every instance ofCubbyHole
.Here's the source for the
CubbyHole
object. The bold code elements provide for thread synchronization:Theclass CubbyHole { private int contents; private boolean available = false; public synchronized int get() { while (available == false) { try { wait(); } catch (InterruptedException e) { } } available = false; notifyAll(); return contents; } public synchronized void put(int value) { while (available == true) { try { wait(); } catch (InterruptedException e) { } } contents = value; available = true; notifyAll(); } }CubbyHole
has two private variables:contents
, which is the current contents of theCubbyHole
, and the boolean variableavailable
, which indicates whether theCubbyHole
contents can be retrieved. Whenavailable
is true theProducer
has just put a new value in theCubbyHole
and theConsumer
has not yet consumed it. TheConsumer
can consume the value in theCubbyHole
only whenavailable
is true.Because
CubbyHole
has synchronized methods, Java provides a unique monitor for each instance ofCubbyHole
(including the one shared by theProducer
and theConsumer
). Whenever control enters a synchronized method, the thread that called the method acquires the monitor for the object whose method has been called. Other threads cannot call a synchronized method on the same object until the monitor is released.
Note: Java Monitors Are Reentrant. The same thread can call a synchronized method on an object for which it already holds the monitor, thereby re-acquiring the monitor.
Whenever the
Producer
calls theCubbyHole
'sput
method, theProducer
acquires the monitor for theCubbyHole
, thereby preventing theConsumer
from calling theCubbyHole
'sget
method. (Thewait
method temporarily releases the monitor as you'll see later.)When thepublic synchronized void put(int value) { // monitor has been acquired by the Producer while (available == true) { try { wait(); } catch (InterruptedException e) { } } contents = value; available = true; notifyAll(); // monitor is released by the Producer }put
method returns, theProducer
releases the monitor thereby unlocking theCubbyHole
.Conversely, whenever the
Consumer
calls theCubbyHole
'sget
method, theConsumer
acquires the monitor for theCubbyHole
thereby preventing theProducer
from calling theput
method.The acquisition and release of a monitor is done automatically and atomically by the Java runtime system. This ensures that race conditions cannot occur in the underlying implementation of the threads, ensuring data integrity.public synchronized int get() { // monitor has been acquired by the Consumer while (available == false) { try { wait(); } catch (InterruptedException e) { } } available = false; notifyAll(); return contents; // monitor is released by the Consumer }
Try this: Remove the lines that are shown in bold in the listing of theCubbyHole
class shown above. Recompile the program and run it again. What happened? Because no explicit effort has been made to synchronize theProducer
andConsumer
threads, theConsumer
consumes with reckless abandon and gets a whole bunch of zeros instead of getting each integer between 0 and 9 exactly once.
Threads of Control |