Synchronized Block in Java

Synchronized block in Java is another way of managing the execution of threads. It is mainly used to perform synchronization on a certain block of code or statements inside the method.

Synchronizing a block of code is more powerful than synchronized method. For example, suppose there are 30 lines of code in a method, but we want to synchronize only 5 lines of code. In this case, we should use a synchronized block.

If we place all the codes of the method in the synchronized block, it will work the same as the synchronized method. The general syntax to synchronize a block of code is as follows:

synchronized(object)
{
  // statements or codes to be synchronized.
}

In the above syntax, an object represents an object reference to be locked or synchronized. The code within synchronized block is all available to only one thread at a time. They will not be available to more than one thread at a time.

Once a thread has entered inside the synchronized block after acquiring lock on the specified object, other threads are blocked to wait for the object lock on the same instance.

Basic Examples of Synchronized Block


Let’s take a simple example program to demonstrate synchronized block or statement. Look at the program code.


Example 1:

public class Table {
void printTable(int x)
{
   synchronized(this) // Synchronized block.
   {
     for(int i = 1; i <= 3; i++)
     {  
        System.out.println(x * i);  
        try
        {  
           Thread.sleep(400);  
        }
        catch(InterruptedException ie)
        {
          System.out.println(ie);
        }  
     }
   }
 }  
}
public class Thread1 extends Thread
{
  Table t;
  Thread1(Table t)
  {
    this.t = t;	
  }
  public void run()
  {  
     t.printTable(2);
  }  
}
public class Thread2 extends Thread
{
  Table t;
  Thread2(Table t)
  {
      this.t = t;	
  }	
  public void run()
  {  
    t.printTable(10); 
  }  
}
public class SynchronizedBlock {
public static void main(String[] args) 
{
   Table t = new Table();	
   Thread1 t1 = new Thread1(t);
   Thread2 t2 = new Thread2(t);
    t1.start(); 
    t2.start();
  }
}
Output:
          2
          4
          6
          10
          20
          30

The code within print Table() method is not available for more than one thread simultaneously.


Example 2:

public class Counter {
  private int count = 0;
 
  public void increment() {
   // Declare synchronized block.
      synchronized (this) {
	   count++; // Critical section
      }
  }
  public int getCount() {
       return count;
  }
}
public class SynchronizedBlockEx {
public static void main(String[] args) throws InterruptedException {
 // Create an object of class Counter.
    Counter counter = new Counter();
 
 // Creating multiple threads
    Thread t1 = new Thread(() -> {
	 for (int i = 0; i < 500; i++) { 
             counter.increment(); 
         } 
    }); 
    Thread t2 = new Thread(() -> {
	 for (int i = 0; i < 500; i++) {
	     counter.increment();
	 }
     });
  // Start both threads.
     t1.start();
     t2.start();

     t1.join();
     t2.join();
     System.out.println("Count: " + counter.getCount());
   }
}
Output:
      Count: 1000

In this example, we have created two threads, t1 and t2, that will increment the same shared count variable 500 times. Both threads will attempt to increase the value of count variable at the same time.

We have started both threads using start() method. After starting both threads, we have called the join() method on each thread. The join() method will make the main thread to wait for both t1 and t2 to finish their execution before displaying the final value of the counter.

This is because, without the join() method, the main thread might display the final value of the counter before the threads have completed their execution, resulting in an incomplete or incorrect value being printed.

The synchronized (this) block will allow only one thread at a time to increment the count variable. This prevents thread interference, where two threads might try to access and modify the count variable simultaneously, which may lead to incorrect or unpredictable results.

Without proper synchronization, the result could be unpredictable due to thread interference.

Advanced Example of Synchronized Block


Let’s take an example in which we will synchronize only a critical portion of your code, leaving non-critical sections unsynchronized.

Example 3:

import java.util.ArrayList;
import java.util.List;
public class ItemManager {
private List<String> itemList = new ArrayList<>();

 // Declare a method to add item to the list in a thread-safe way.
    public void addItemToList(String item) {
    // Non-critical section
       System.out.println("Adding item: " + item);

    // Critical section of the code.
       synchronized (itemList) { 
           itemList.add(item); // Adding item in the list.
       }
    // Non-critical section
       System.out.println("Item added successfully.");
    }

 // Declare a method to display the list.
    public void displayItems() {
        synchronized (itemList) {
            System.out.println("Current List Items: " + itemList);
        }
    }
}
public class SynchronizedBlockEx {
public static void main(String[] args) throws InterruptedException {
// Create an object of class ItemManager.	
   ItemManager manager = new ItemManager();

// Creating thread 1.
   Thread t1 = new Thread(() -> {
        manager.addItemToList("Item 1");
   });

// Creating thread 2.
   Thread t2 = new Thread(() -> {
        manager.addItemToList("Item 2");
   });

// Start both threads by calling start() method.
   t1.start();
   t2.start();

// Wait for both threads to finish their execution completely.
   t1.join();
   t2.join();

// Display the final list of items.
   manager.displayItems();
  }
}
Output:
      Adding item: Item 1
      Item added successfully.
      Adding item: Item 2
      Item added successfully.
      Current List Items: [Item 1, Item 2]

In this example, the addItemToList() method uses a synchronized block to allow only one thread can add items to the list at a time. We have created two threads, t1 and t2 to add items to the list.

Both threads are started, and the join() method makes the main thread to wait for both threads to complete their tasks before displaying the final list.

Nested Synchronized Blocks in Java


Java allows you to nest synchronized blocks for synchronizing on multiple objects within a method. Let’s take a simple example program based on it.

Example 4:

public class NestedSynBlocks {
// Declare a method to perform a complex operation with two locks.
   public void complexOperation(Object lock1, Object lock2) {
    // Nested synchronized blocks.
       synchronized (lock1) {
          System.out.println("Acquired lock on lock1, and performed the first operation.");

          synchronized (lock2) {
              System.out.println("Acquired lock on lock2, and performed the second operation.");
          }
       }
    }
}
public class SynchronizedBlockEx {
public static void main(String[] args) throws InterruptedException {
     NestedSynBlocks obj = new NestedSynBlocks();
     Object lock1 = new Object();
     Object lock2 = new Object();

  // Creating thread 1 and thread 2. Both threads now acquire locks in the same order.
     Thread t1 = new Thread(() -> {
         obj.complexOperation(lock1, lock2);  // lock1 first, then lock2.
     });

     Thread t2 = new Thread(() -> {
         obj.complexOperation(lock1, lock2);  // lock1 first, then lock2
     });
     t1.start();
     t2.start();

     t1.join();
     t2.join();
     System.out.println("Both operations completed.");
  }
}
Output:
       Acquired lock on lock1, and performed the first operation.
       Acquired lock on lock2, and performed the second operation.
       Acquired lock on lock1, and performed the first operation.
       Acquired lock on lock2, and performed the second operation.
       Both operations completed.

Best Practices for Using Synchronized Block


Here, we have listed some key points that you should keep in kind while using synchronized blocks in Java. They are:

  • Synchronize only a certain block of code that deals with shared resources to improve the performance of your program.
  • Use private final objects for locking to avoid unintended locking.
  • Always try to avoid long synchronized blocks.
  • Keep the synchronized blocks as short as possible.
  • Always acquire locks in the same order to avoid the deadlock situation.
  • Avoid holding multiple locks at the same time if possible.

Synchronized Method vs Synchronized Block


The following are the differences between the synchronized method and synchronized block.

FeatureSynchronized MethodSynchronized Block
Scope of synchronizationSynchronizes the entire method.Synchronizes only a specific block or section of code.
PerformanceLess efficient due to locking the entire method.More efficient because only a small portion of the code is locked.
Locking objectImplicitly locks “this” for instance methods or the “class” for static methods.Can lock any specified object.
Use caseUse when you want to synchronize the entire method.Use when you want to synchronize a certain portion of the code.
EfficiencyLess efficient.More efficient.
Code readabilityEasier to read and understand.Slightly more complex.

Hope that this tutorial has covered important points related to synchronized block in Java with example program. I hope that you will have understood the basic concept of synchronized block.

If you get any incorrect in this tutorial, please inform to our team through email. Your email will be valuable for us. In the next, we will understand inter thread communication in Java through examples.
Thanks for reading!!!