June 17, 2022

Synchronization in Java Using Synchronized Keyword

In a multi-threaded program, shared resource is always a bone of contention among threads. If you have a critical section in you code where you are modifying a shared resource you would like the access to that critical section restricted so that at any given time only a single thread can access the critical section code and use the shared resource. The process by which you can achieve this is called synchronization in Java and you will use synchronized keyword in Java for synchronization.

How synchronization in Java works

Every object in Java has a single lock (also called monitor) associated with it. When a thread enters a synchronized method or synchronized block it acquires that lock. All other threads attempting to execute the same code (in synchronized method or synchronized block) have to wait for the first thread to finish and release the lock.

Here note that once a thread calls any synchronized method and has acquired a lock, that object is locked. Which means none of the synchronized methods of that object can be called until the lock is released by the acquiring thread. Thus the lock is at object level and shared by all the synchronized methods of a specific object.

To see how to synchronize at class level rather than at instance level refer this post- Synchronization with static keyword in Java.

Using synchronized keyword in Java

In order to synchronize your code in Java you can use either of the following two ways-

  • Synchronizing the whole method (Synchronized method)
  • Synchronizing lines of code with in a method (Synchronized statement or Synchronized block)

Synchronized method in Java

To make a method synchronized in Java, simply add the synchronized keyword to its declaration.

General Form of synchronized method in Java

synchronized <returntype> method_name(args){
  ...
  ...
}

Synchronized method Java example

Let’s see an example of synchronized method in Java, here we have two methods; in one of the method there is a for loop running from 1 to 5 and those values are displayed, in another method for loop runs from 5 to 1 and values are displayed. What is needed here is that which ever method runs first should display all values i.e. 1,2,3,4,5 and 5,4,3,2,1. First let’s see what happens if synchronization is not done here.

// Class whose object will be shared
class Counter{
  public void increment(){
    for(int i = 1; i <= 5 ; i++){
      System.out.println(Thread.currentThread().getName() + " i - " + i);
      try {
        Thread.sleep(50);
      } catch (InterruptedException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
      }
    } 
  }
  public void decrement(){
    for(int i = 5; i > 0 ; i--){
      System.out.println(Thread.currentThread().getName() + " i - " + i);		   
    } 
  }
}

public class SynchronizedDemo {
  public static void main(String[] args) throws InterruptedException {
    // One object shared among both threads
    Counter ctr = new Counter();
    Thread t1 = new Thread(){
      @Override
      public void run() {
        ctr.increment();
      }
    };		
    Thread t2 = new Thread(){
      @Override
      public void run() {
        ctr.decrement();
      }
    };
		
    t1.start();
    t2.start();
  }
}
Output
Thread-1 i - 5
Thread-0 i - 1
Thread-1 i - 4
Thread-1 i - 3
Thread-1 i - 2
Thread-1 i - 1
Thread-0 i - 2
Thread-0 i - 3
Thread-0 i - 4
Thread-0 i – 5

As you can see that the two threads are interleaving and the output is mixed.

To ensure that the all values are displayed you can synchronize the methods.

// Class whose object will be shared
class Counter{
  public synchronized void increment(){
    for(int i = 1; i <= 5 ; i++){
      System.out.println(Thread.currentThread().getName() + " i - " + i);
      try {
        Thread.sleep(50);
      } catch (InterruptedException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
      }
    } 
  }
  public synchronized void decrement(){
    for(int i = 5; i > 0 ; i--){
      System.out.println(Thread.currentThread().getName() + " i - " + i);		   
    } 
  }
}

public class SynchronizedDemo {
  public static void main(String[] args) throws InterruptedException {
    // One object shared among both threads
    Counter ctr = new Counter();
    Thread t1 = new Thread(){
      @Override
      public void run() {
        ctr.increment();
      }
    };
    
    Thread t2 = new Thread(){
      @Override
      public void run() {
        ctr.decrement();
      }
    };
    
    t1.start();
    t2.start();
  }
}
Output
Thread-0 i - 1
Thread-0 i - 2
Thread-0 i - 3
Thread-0 i - 4
Thread-0 i - 5
Thread-1 i - 5
Thread-1 i - 4
Thread-1 i - 3
Thread-1 i - 2
Thread-1 i – 1

As you can see from the output, once one of the thread has the object lock another thread can’t execute any of the synchronized method of that object. If one of the thread has acquired the lock and started executing the synchronized increment() method another thread can’t execute the decrement() method as that is also synchronized.

Synchronized block in Java

Another way to achieve thread synchronization is with synchronized blocks in Java. Synchronized statements must specify the object that provides the intrinsic lock.

General Form of synchronized block in Java
Synchronized(object_reference){
  // code block
}

Synchronized block is useful and provides performance improvement in the case when-

  • You have a big method but the critical section (code where shared resource is modified) is with in few lines in that big method then you can synchronize only that critical section rather than synchronizing the whole method.
  • You have some object which was not designed to be run in multi-threaded environment and the methods are not synchronized. In that kind of scenario you can put the call to those methods in synchronized blocks.
We can take the same example as used before. Now, rather than synchronizing the methods we can use synchronized blocks where the methods are called.
// Class whose object will be shared
class Counter{
  public void increment(){
    for(int i = 1; i <= 5 ; i++){
      System.out.println(Thread.currentThread().getName() + " i - " + i);
      try {
        Thread.sleep(50);
      } catch (InterruptedException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
      }
    } 
  }
  public void decrement(){
    for(int i = 5; i > 0 ; i--){
      System.out.println(Thread.currentThread().getName() + " i - " + i);		   
    } 
  }
}

public class SynchronizedDemo {
  public static void main(String[] args) throws InterruptedException {
    // One object shared among both threads
    Counter ctr = new Counter();
    Thread t1 = new Thread(){
      @Override
      public void run() {
        // Method call in synchronized block
        synchronized(ctr){
          ctr.increment();
        }
      }
    };
    
    Thread t2 = new Thread(){
      @Override
      public void run() {
        // Method call in synchronized block
        synchronized(ctr){
          ctr.decrement();
        }
      }
    };
    
    t1.start();
    t2.start();
  }
}

You can also put the code in synchronized block rather than synchronizing the method.

class Counter{
  public void increment(){
    // synchronized block
    synchronized(this){
      for(int i = 1; i <= 5 ; i++){
        System.out.println(Thread.currentThread().getName() + " i - " + i);
        try {
          Thread.sleep(50);
        } catch (InterruptedException e) {
          // TODO Auto-generated catch block
          e.printStackTrace();
        }
      } 
    }
  }
  public void decrement(){
    synchronized(this){
      for(int i = 5; i > 0 ; i--){
        System.out.println(Thread.currentThread().getName() + " i - " + i);		   
      } 
    }
  }
}

Important points about synchronization in Java

  • Synchronization in Java is built around an internal entity known as the intrinsic lock or monitor lock.
  • Every object has an intrinsic lock associated with it. A thread that needs exclusive and consistent access to an object's fields has to acquire the object's intrinsic lock before accessing them, and then release the intrinsic lock when it's done with them.
  • When a thread invokes a synchronized method, it automatically acquires the intrinsic lock for that method's object and releases it when the method returns. The lock release occurs even if the return was caused by an uncaught exception.
  • A thread cannot acquire a lock owned by another thread. But a thread can acquire a lock that it already owns. Allowing a thread to acquire the same lock more than once enables reentrant synchronization.
  • Synchronization in Java degrades performance as the threads can use synchronized code sequentially. Try to use synchronized block to synchronize the critical section rather than synchronizing the whole method.
  • In case of synchronized keyword there are no separate locks for read and write and there is no provision to increase the performance by allowing concurrent reads. Try using ReentrantReadWriteLock in case there are more reads than writes.
  • Trying to use string object as lock with synchronized block is not recommended. It is because of the String pool where literal strings are shared. So more than one strings, though completely unrelated, may share the same object reference. This may result in unexpected behavior.

That's all for the topic Synchronization in Java Using Synchronized Keyword. If something is missing or you have something to share about the topic please write a comment.


You may also like

No comments:

Post a Comment