In this section, we will learn what the synchronization means and how to use it in Java.
Note: we’re assuming you’re already familiar with the Java Memory Model (JMM).
What is Synchronization in Java?
Synchronization comes when we have multiple threads in a Java program and want to coordinate their use of a shared resource in order to create a predictable outcome.
For example, let’s say there’s a method and multiple threads want to access it and run it simultaneously! Here, if in the target method, there’s a shared resource like an instance variable or a static variable, etc. there’s no way to predict what will be the final result of that variable! This is because each thread is trying to run the target method in its stack at the same time and so at the end, we will end up getting an unpredictable result.
Now, by synchronizing that method, we’re rest assured that only one thread at a time can access the target method and while that thread is running the instructions there, the other threads will be blocked until the work of the current thread is done.
Note: in the rest of this section, we will explain how this locking works in Java.
Java synchronized keyword
The synchronization in Java is done using the `synchronized` keyword. Simply add this keyword to a method or a block of instructions that want it to be synchronized, and then, at runtime, JVM will schedule threads to enter the target method/block one at a time.
First, let’s see the syntax of using the synchronized keyword in practice; after that, we will explain how synchronization is done behind the scenes when we reach to the object-monitor section.
Java synchronized Keyword Syntax:
This is the syntax of using the synchronized keyword for methods:
Access-specifier synchronized data-type methodName(){…}
As you can see, between the access-specifier and the data-type of the target method, we put the synchronized keyword.
For example:
public synchronized void slp(){…}
Now, if you have a block of code and want to synchronize it, this is how we can do so:
synchronized (instance-object){…}
A block of code starts with the keyword `synchronized`, after that, we put a pair of parentheses and inside that we define an instance object that wants to use its monitor’s block.
Now the question is: what is this `monitor` thing and why we need it? Well, continue reading the section below to learn about monitors.
Java Object Monitor
As mentioned in the JMM section, a monitor is a program construct that JVM associates one to each object we create in a program.
Within this monitor, there’s something called `lock`. The logic behind the `synchronized` keyword is based on this `lock`.
Note that other than lock, a monitor has two sets called `Entry-Set` and `Waiting-Set`. We will get into the `waiting-set` at later sections but for the `entry-set`, this is where all threads go into when they want to access a method of an object that is synchronized.
For example, let’s say there’s a method called `m1` which is synchronized and it belongs to the `obj` object. Now, if multiple threads want to invoke the body of this method, they first enter the `entry-set` of the obj’s monitor.
Here, the JVM chooses which thread in the entry-set can `acquire` the monitor’s lock. Based on some logics, finally one of the threads acquires the monitor’s lock and runs the instructions for the `m1` method.
Note that at this stage, the other threads will be blocked in the `entry-set` until the work of the current thread is done and exits from the body of the `m1` method (Or simply releases the lock using other logics like by calling the wait() method).
Basically, a lock of a monitor can only be acquired by one thread at a time! Now, as long as that thread has the lock, no-other thread can enter any synchronized method of the target object! It doesn’t matter if the other threads want to enter the same method or another method of the same object that is synchronized as well.
(If a thread wants to invoke part of an object that is not synchronized, it will not enter to the entry-set of the related monitor, thus it can run that part of the object without any blockings.)
After that, a thread `releases` the lock, the lock is now back to the monitor and so at this time the JVM decides which one of the blocked threads in the `entry-set` can `acquire` the lock next.
This process goes on until all the blocked threads get the lock and run the target method.
Meanwhile, if another thread enters the entry-set, that thread will be blocked as well until JVM decides it is the turn of that thread and it can acquire the lock of the monitor.
Synchronized Instance Methods
Alright, to begin with, let’s first see how we can synchronize an instance method in Java.
Note that the only thing we need to do is to prefix the target method with the synchronized keyword, as the example below shows now.
Example: synchronizing an instance method
public class Main { public static void main(String[] args) { Main main = new Main(); Thread t1 = new Thread(main::increment, "Thread-one"); Thread t2 = new Thread(main::increment, "Thread two"); t1.start(); t2.start(); } public synchronized void increment(){ for (int i = 0; i<5; i++){ try { Thread.sleep(1000); System.out.println("This is "+Thread.currentThread().getName()); } catch (InterruptedException e) { e.printStackTrace(); } } } }
Output:
This is Thread-one This is Thread-one This is Thread-one This is Thread-one This is Thread-one This is Thread two This is Thread two This is Thread two This is Thread two This is Thread two
In this example, the two threads, t1 and t2 are trying to access the `increment` method. But here, because this method is synchronized, what happens is that:
- First, both threads will get into the `main` object’s monitor (in the entry-set).
- Now, JVM decides which one can take the lock of the monitor and invoke the body of the `increment()` method in their stack. (For this example, the JVM decided to give the lock to the thread-one first, but you may get a different result if you try to run this program).
- While the thread one is running the body of the `increment()` method, the thread-two is blocked in the entry-set and it will wait until the work of the `t1` is done.
- After the work of the `t1` thread is done, it will release the lock (by exiting from the method), and so now JVM allows the second thread to acquire the lock and invoke the body of the `increment()` method.
That’s how we got an ordered output from this example!
Note that if we didn’t synchronize this method, both threads would’ve invoked the body of the method and run it simultaneously, which would’ve ended up getting an unordered output.
Synchronized Static Methods
The second type of method that we can synchronize is static methods.
At first, you might be surprised that a static method can be synchronized as well! Mainly because we said that synchronization is done using the monitor of an object! So, how can a static method be associated with an object?!
Well, the truce is, classes are associated with their own objects as well! For example, if you have a class named Multi.class, at runtime, there’s an object associated with this class behind the scene. So, the monitor of that associated object will be used to acquire and release the lock.
Example: synchronizing a static method
public class Main { public static void main(String[] args) { Thread t1 = new Thread(Main::increment, "Thread-one"); Thread t2 = new Thread(Main::increment, "Thread two"); t1.start(); t2.start(); } public synchronized static void increment(){ for (int i = 0; i<5; i++){ try { Thread.sleep(1000); System.out.println("This is "+Thread.currentThread().getName()); } catch (InterruptedException e) { e.printStackTrace(); } } } }
Output:
This is Thread two This is Thread two This is Thread two This is Thread two This is Thread two This is Thread-one This is Thread-one This is Thread-one This is Thread-one This is Thread-one
Synchronized Blocks in Instance Methods
Another area where we can apply synchronization is when there’s a block of code somewhere in a program.
For example, let’s say there’s a method and inside that method, there’s only a portion of instructions that we want to synchronize. This is where we can put those instructions in a block and use the synchronized keyword to synchronize this specific block instead of the whole method! So, only those threads that want to get into the target block are required to get the lock of the related object and may or may not be blocked.
Example: synchronizing a block in an instance method
public class Main { public static void main(String[] args) { Main main = new Main(); Thread t1 = new Thread(main::increment, "Thread-one"); Thread t2 = new Thread(main::increment, "Thread two"); t1.start(); t2.start(); } public void increment(){ System.out.println(Thread.currentThread().getName()); synchronized (Main.class){ for (int i = 0; i<3; i++){ try { Thread.sleep(1000); System.out.println("This is "+Thread.currentThread().getName()); } catch (InterruptedException e) { e.printStackTrace(); } } } } }
Output:
Thread-one Thread two This is Thread-one This is Thread-one This is Thread-one This is Thread two This is Thread two This is Thread two
Here, both threads executed the first statement of the `increment()` method simultaneously, but after that, the threads faced with a synchronized block. So here, both entered the monitor of the object that the method was invoked with and the first thread acquired the lock and ran the rest of instructions of this method while the other thread got blocked until the work of the thread-one was done.
So, as you can see, using a synchronized block, we can block a specific part of a method instead of synchronizing the whole body!
Synchronized Blocks in Static Methods
Just like the synchronized blocks in an instance method, we can have synchronized blocks in static methods as well.
The only thing to remember is that if we want to use the lock of the associated object of the owner class, we need to put the name of that class with the extension of the `.class` in the parentheses of blocks that are in a static method.
For example, if the name of the class is MyClass, then what we put in the parentheses of the block will be `MyClass.class`. Again, this is only needed if we want to use the associated object of the owner class! There’s no limit to use other instances if necessary.
Example: synchronizing a block in a static method
public class Main { public static void main(String[] args) { Thread t1 = new Thread(Main::increment, "Thread-one"); Thread t2 = new Thread(Main::increment, "Thread two"); t1.start(); t2.start(); } public static void increment(){ System.out.println(Thread.currentThread().getName()); synchronized (Main.class){ for (int i = 0; i<3; i++){ try { Thread.sleep(1000); System.out.println("This is "+Thread.currentThread().getName()); } catch (InterruptedException e) { e.printStackTrace(); } } } } }
Output:
Thread-one Thread two This is Thread-one This is Thread-one This is Thread-one This is Thread two This is Thread two This is Thread two
Note that both threads in this example ran the first statement of the `increment()` method. But when it came to the synchronized block of this method, the thread-one acquired the lock, while the thread-two was blocked in the entry-set of the associated monitor until the work of the thread-one was done.
FAQ:
1- What is Locking in Java?
Locking is when a thread takes control of a synchronized method/ block. It means, while the target thread is running the instructions of the target method/ block, no other threads can access the same method! Note that a lock belongs to an object’s monitor.
2- What is Mutex in Java? (Mutual Exclusion)
The concept of mutual exclusion comes when we’re dealing with synchronization in a multithreaded program. The idea is that no two threads can access a synchronized method/ block at the same time! Basically, if one thread currently running the body of a synchronized method/ block, the other threads must wait (they will be blocked) until the current thread released the lock of the target method.