We have seen in our previous posts, in a typical Java application, multiple threads run in parallel to complete program execution. This may lead to a situation where multiple threads try to access the same resource simultaneously. This can produce unforeseen and erroneous results.
Therefore, a mechanism is required which ensures only one thread can access a resource at a given point of time. Java has the concept synchronization for this mechanism. This allows the programmer to synchronize tasks using synchronized blocks/methods.
Therefore, a mechanism is required which ensures only one thread can access a resource at a given point of time. Java has the concept synchronization for this mechanism. This allows the programmer to synchronize tasks using synchronized blocks/methods.
- Synchronized blocks are marked with the keyword "synchronized".
- A synchronized block is synchronized on some object. This ensures mutually exclusive access to the shared resource and prevents data race.
- Only one thread can execute inside a synchronized block at a time. All other threads attempting to enter the synchronized block are blocked until the thread inside the block exits the block.
- Synchronization also prevents reordering of the code statements by the compiler.
- Before entering a synchronized block, a thread needs to acquire the lock, at this point it reads data from main memory than cache and when it releases the lock, it flushes write operation into main memory which eliminates memory inconsistency errors.
Synchronization is implemented with a concept called monitors. Only one thread can own a monitor at a given time.
When a thread acquires a lock, it is said to have entered the monitor. All other threads attempting to enter the locked monitor will be suspended until the first thread exits the monitor.
Monitor
A monitor allows threads to have both mutual exclusion and ability to block (wait) for a certain condition to become true. It is also called intrinsic lock. A monitor can also signal other threads that a condition has been met. It has both a lock and wait set. Any object can serve as a monitor.
In JVM, every class and object is logically associated with a monitor. To implement mutual exclusion capability of monitors, a lock (also called mutex) is associated with each object and class. This is called semaphore in OS terms.
If one thread owns a lock on some data, no other thread can access it until the lock is released. It is a good idea to say that each object has a monitor since each object can have its own critical section and it is capable of monitoring the thread sequence.
There are two ways to gain access to object monitor -
- Add the synchronized keyword to the method
public synchronized int method(){
...
}
- Add the synchronized block
public int method(){
synchronized(someObject){
...
}
}
Static vs non-static synchronized methods
From the previous discussion, we know that synchronized is an implementation of the Monitor.
Object x = new Object();
Object y = new Object();
...
synchronized(x){
A();
}
...
synchronized(y){
B();
}
...
synchronized(c){
C();
}
A thread running C() would block another thread from entering the block of code protecting A() as they are synchronized on the object x. However, a thread could enter the block of code protecting B() as it is synchronized on object y.
When we use the synchronized modifier on an instance method (non-static), it is very similar to having a synchronized block with "this" as the argument.
public synchronized void A() {
doStuff();
}
...
public void B() {
synchronized(this) {
doStuff();
}
}
Both A() and B() will behave in the same way.
If we have a static method with the synchronized modifier, it is very similar to having a synchronized block with ClassName.class as the argument.
class MyClass {
public void static synchronized A() {
doStaticStuff();
}
public static void B() {
synchronized(MyClass.class) {
doStaticStuff();
}
}
public void C() {
synchronized(this.getClass()) {
doStuff();
}
}
}
In the above code, A() and B() will behave in the same way. An executing thread will also be blocked from accessing the code in C() as it is synchronizing on the same object. (source)
Code example
Following code shows the concepts in action -
The output of the above program will be same every time we run the program -
Output
Sending Hello World!
Hello World! sent
Sending Bye World!
Bye World! sent
Conclusion
Congratulations!! 🙋 today we discussed the concepts of synchronization, monitor, static and non-static synchronization with their implementations. I hope you enjoyed this post.
You can find the complete code of this project on my GitHub in this commit. Feel free to fork or open issues, if any.
I would love to hear your thoughts on this and would like have suggestions from you to make it better.
Happy Coding 😊
first thank you for the great guide
ReplyDelete"Synchronization also prevents reordering of the code statements by the compiler." i think that statement isn't correct
in the first example of "Static vs non-static synchronized methods" i think you typed c instead of x in:
synchronized(c){
C();
}
which i think was intended to be
synchronized(x){
C();
}