Technology
Synchronized Methods vs Wait/Notify: An in-depth Analysis
Introduction to Synchronized Methods
Synchronized Methods
Protecting Global Variables
Synchronized is used to protect a set of global variables so that you avoid lost updates. In other words, it guarantees atomicity. It ensures that all changes made in the synchronized block are flushed to the shared memory. Furthermore, all reads within the synchronized block read from the shared data and not from cache.
This mechanism can include the invalidation of cached read/write data, but let's focus on the logical aspect. Synchronized blocks are statically enforced, meaning that the release of the acquired lock is baked in when exiting the synchronized block.
Usage of Synchronized Blocks
Synchronized is not enough in many cases. Let's say there is a global variable that is set by thread1 and read by thread2. If thread2 enters a synchronized block and finds that the variable is not set, it would need a loop construct to keep checking if the value has arrived. And every check means entering and exiting the synchronized block, which can lead to a performance overhead. Moreover, there is no fair guarantee in case of more than one thread waiting for this variable to be populated.
Object Monitor
Object monitor new Object();synchronized(monitor) { // Code to be protected from concurrent access}
When a thread enters the above block, it is said to have acquired the monitor. And when it exits, it is said to have released the monitor. Two threads invoking synchronized on the same monitor object means only one thread can enter the protected block. When the monitor is not defined, the object itself where the synchronized block is defined becomes the monitor.
Wait/Notify and Marker Objects
Thread Coordination with Wait/Notify
Before we continue, let's define a monitor. An object monitor is used to manage synchronization.
If thread2 finds that the value is still not set after entering a synchronized block, it can register itself as an interested party with the monitor by invoking monitor.wait. The moment this is done, the monitor releases thread2.
Queue Mechanism with Wait
Thread2 is put into an internal wait queue associated with the monitor object. Since thread2 has released the monitor, thread1 can now enter the synchronized block, set the value of the variable, and then exit the synchronized block.
Notify Mechanism
When thread1 can now enter the synchronized block and get the value, it can't do this implicitly. The runtime does not know if thread2 has made progress based on any state change. The decision to notify is left entirely up to the threads to communicate and coordinate among themselves. Thread2 should tell the runtime to allow one thread to check if it can make progress, which is done by invoking notify. On notify, the runtime would just remove one thread (thread1 in this case) from the internal wait queue and let it continue. While thread1 can acquire the monitor again and continue its execution, it doesn't mean that its condition variable is set and thread2 can make progress.
Notification and Priority
It's possible for a thread to be notified, removed from the internal queue, and set to continue execution due to the underlying hardware/OS behavior often called spurious wake-up. However, since every consumer thread must recheck for the condition, this isn't an issue. Sometimes, more than one thread may be waiting for different conditions. For example, if the variable is a list and thread2 is waiting for the list size to be less than 10 while another thread, thread3, is waiting for the list size to be 10 or more, the runtime only provides queuing and thread scheduling on notification. There is no way to know if thread2 or thread3 should be scheduled for execution. Thread1 knows the size of the list. But it cannot communicate this directly since the goal is to isolate the producer thread (thread1) from knowing the conditions associated with consumer threads (thread2, thread3). You can change the consumer-side code without affecting the producer-side code. This is where condition variables are supported in the Java concurrency package and are preferred over wait/notify/notifyall.
Conclusion
While synchronized methods provide a straightforward way to handle concurrency, they may not be sufficient in all scenarios. Wait/notify provides a way to have fine-grained control over thread coordination and allows for more efficient and flexible synchronization. However, Java's concurrency package also supports condition variables on locks, which are preferred over traditional wait/notify methods.
Related Keywords
synchronized methods wait notify Java Concurrency