Guide to the Synchronized Keyword in Java

1. Overview

This quick article will be an intro to using the synchronized block in Java.

Simply put, in a multi-threaded environment, a race condition occurs when two or more threads attempt to update mutable shared data at the same time. Java offers a mechanism to avoid race conditions by synchronizing thread access to shared data.

A piece of logic marked with synchronized becomes a synchronized block, allowing only one thread to execute at any given time.

2. Why Synchronization?

Let’s consider a typical race condition where we calculate the sum and multiple threads execute the calculate() method:

public class VietMXSynchronizedMethods {

    private int sum = 0;

    public void calculate() {
        setSum(getSum() + 1);
    }

    // standard setters and getters
}

And let’s write a simple test:

@Test
public void givenMultiThread_whenNonSyncMethod() {
    ExecutorService service = Executors.newFixedThreadPool(3);
    VietMXSSynchronizedMethods summation = new VietMXSSynchronizedMethods();

    IntStream.range(0, 1000)
      .forEach(count -> service.submit(summation::calculate));
    service.awaitTermination(1000, TimeUnit.MILLISECONDS);

    assertEquals(1000, summation.getSum());
}

We’re simply using an ExecutorService with a 3-threads pool to execute the calculate() 1000 times.

If we would execute this serially, the expected output would be 1000, but our multi-threaded execution fails almost every time with an inconsistent actual output e.g.:

java.lang.AssertionError: expected:<1000> but was:<965>
at org.junit.Assert.fail(Assert.java:88)
at org.junit.Assert.failNotEquals(Assert.java:834)
...

This result is of course not unexpected.

A simple way to avoid the race condition is to make the operation thread-safe by using the synchronized keyword.

3. The Synchronized Keyword

The synchronized keyword can be used on different levels:

  • Instance methods
  • Static methods
  • Code blocks

When we use a synchronized block, internally Java uses a monitor also known as monitor lock or intrinsic lock, to provide synchronization. These monitors are bound to an object, thus all synchronized blocks of the same object can have only one thread executing them at the same time.

3.1. Synchronized Instance Methods

Simply add the synchronized keyword in the method declaration to make the method synchronized:

public synchronized void synchronisedCalculate() {
    setSum(getSum() + 1);
}

Notice that once we synchronize the method, the test case passes, with actual output as 1000:

@Test
public void givenMultiThread_whenMethodSync() {
    ExecutorService service = Executors.newFixedThreadPool(3);
    SynchronizedMethods method = new SynchronizedMethods();

    IntStream.range(0, 1000)
      .forEach(count -> service.submit(method::synchronisedCalculate));
    service.awaitTermination(1000, TimeUnit.MILLISECONDS);

    assertEquals(1000, method.getSum());
}

Instance methods are synchronized over the instance of the class owning the method. Which means only one thread per instance of the class can execute this method.

3.2. Synchronized StatiMethods

Static methods are synchronized just like instance methods:

public static synchronized void syncStaticCalculate() {
     staticSum = staticSum + 1;
 }

These methods are synchronized on the Class object associated with the class and since only one Class object exists per JVM per class, only one thread can execute inside a static synchronized method per class, irrespective of the number of instances it has.

Let’s test it:

@Test
public void givenMultiThread_whenStaticSyncMethod() {
    ExecutorService service = Executors.newCachedThreadPool();

    IntStream.range(0, 1000)
      .forEach(count -> 
        service.submit(VietMXSSynchronizedMethods::syncStaticCalculate));
    service.awaitTermination(100, TimeUnit.MILLISECONDS);

    assertEquals(1000, VietMXSSynchronizedMethods.staticSum);
}

3.3. Synchronized Blocks Within Methods

Sometimes we do not want to synchronize the entire method but only some instructions within it. This can be achieved by applying synchronized to a block:

public void performSynchronisedTask() {
    synchronized (this) {
        setCount(getCount()+1);
    }
}

Let’s test the change:

@Test
public void givenMultiThread_whenBlockSync() {
    ExecutorService service = Executors.newFixedThreadPool(3);
    VietMXSSynchronizedBlocks synchronizedBlocks = new VietMXSSynchronizedBlocks();

    IntStream.range(0, 1000)
      .forEach(count -> 
        service.submit(synchronizedBlocks::performSynchronisedTask));
    service.awaitTermination(100, TimeUnit.MILLISECONDS);

    assertEquals(1000, synchronizedBlocks.getCount());
}

Notice that we passed a parameter this to the synchronized block. This is the monitor object, the code inside the block gets synchronized on the monitor object. Simply put, only one thread per monitor object can execute inside that block of code.

In case the method is static, we would pass the class name in place of the object reference. And the class would be a monitor for synchronization of the block:

public static void performStaticSyncTask(){
    synchronized (SynchronisedBlocks.class) {
        setStaticCount(getStaticCount() + 1);
    }
}

Let’s test the block inside the static method:

@Test
public void givenMultiThread_whenStaticSyncBlock() {
    ExecutorService service = Executors.newCachedThreadPool();

    IntStream.range(0, 1000)
      .forEach(count -> 
        service.submit(VietMXSSynchronizedBlocks::performStaticSyncTask));
    service.awaitTermination(100, TimeUnit.MILLISECONDS);

    assertEquals(1000, VietMXSSynchronizedBlocks.getStaticCount());
}

3.4. Reentrancy

The lock behind the synchronized methods and blocks is reentrant. That is, the current thread can acquire the same synchronized lock over and over again while holding it:

Object lock = new Object();
synchronized (lock) {
    System.out.println("First time acquiring it");

    synchronized (lock) {
        System.out.println("Entering again");

         synchronized (lock) {
             System.out.println("And again");
         }
    }
}

As shown above, while we’re in a synchronized block, we can acquire the same monitor lock repeatedly.

4. Conclusion

In this quick article, we have seen different ways of using the synchronized keyword to achieve thread synchronization.

We also explored how a race condition can impact our application, and how synchronization helps us avoid that. For more about thread safety using locks in Java refer to our java.util.concurrent.Locks article.

The complete code for this tutorial is available over on GitHub.

Related posts:

Java Program to Implement Circular Singly Linked List
Java 8 Streams peek() API
Java Program to Compute Discrete Fourier Transform Using Naive Approach
Java Program to Perform Quick Sort on Large Number of Elements
Java Program to Implement the Monoalphabetic Cypher
Multi Dimensional ArrayList in Java
Case-Insensitive String Matching in Java
Assertions in JUnit 4 and JUnit 5
Java Program to Implement Doubly Linked List
Java Program to Find the Minimum Element of a Rotated Sorted Array using Binary Search approach
So sánh HashSet, LinkedHashSet và TreeSet trong Java
Hướng dẫn sử dụng Java String, StringBuffer và StringBuilder
Spring @RequestMapping New Shortcut Annotations
Array to String Conversions
Use Liquibase to Safely Evolve Your Database Schema
Java Program to Implement Threaded Binary Tree
Spring WebClient vs. RestTemplate
So sánh HashMap và Hashtable trong Java
SOAP Web service: Upload và Download file sử dụng MTOM trong JAX-WS
So sánh Array và ArrayList trong Java
Guide to Spring Cloud Kubernetes
Tạo ứng dụng Java RESTful Client với thư viện OkHttp
Java Program to Convert a Decimal Number to Binary Number using Stacks
HttpClient with SSL
The Spring @Controller and @RestController Annotations
Using JWT with Spring Security OAuth (legacy stack)
Java Program to Find Transitive Closure of a Graph
Java Program to Implement Slicker Algorithm that avoids Triangulation to Find Area of a Polygon
A Guide to LinkedHashMap in Java
Java Program to Implement K Way Merge Algorithm
Java Program to Perform Partial Key Search in a K-D Tree
Using a Mutex Object in Java