Introduction

In a multi-threaded environment, we often encounter resource competition, such as multiple threads going to modify the same shared variable at the same time, it is necessary to perform some processing of the resource access method to ensure that only one thread accesses it at the same time.

Java provides the synchronized keyword to facilitate us to achieve the above operation.

Why synchronized

Let’s take an example where we create a class that provides a setSum method.

1
2
3
4
5
6
7
8
public class SynchronizedMethods {

    private int sum = 0;

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

If we call this calculate method in a multi-threaded environment.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
    @Test
    public void givenMultiThread_whenNonSyncMethod() throws InterruptedException {
        ExecutorService service = Executors.newFixedThreadPool(3);
        SynchronizedMethods summation = new SynchronizedMethods();

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

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

According to the above method, we expect to return 1000, but in practice it is basically impossible to get the value of 1000 because of the negative impact of simultaneous operations on the same resource in a multi-threaded environment.

So how can we build a thread-safe environment?

Synchronized keyword

Java provides a variety of thread-safe methods, this article focuses on the Synchronized keyword, Synchronized keyword can have many forms:

  • Instance methods
  • Static methods
  • Code blocks

When we use synchronized, java will add a lock on the corresponding object, so that the methods waiting for the lock on the same object must be executed sequentially, thus ensuring thread safety.

Synchronized Instance Methods

The Synchronized keyword can be placed in front of the instance method.

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

Look at the result of the call.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
@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());
}

Here synchronized will lock the instance object of the method, and only the thread that gets the lock on the instance object will be able to execute it in multiple threads.

Synchronized Static Methods

The Synchronized keyword can also be used in front of static methods.

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

Synchronized in front of a static method locks a different object than in front of an instance method. The object locked in front of the static method is the Class itself, because there is only one Class in the JVM, so no matter how many instances of the Class there are, there will only be one thread that can execute the method at the same time.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
    @Test
    public void givenMultiThread_whenStaticSyncMethod() throws InterruptedException {
        ExecutorService service = Executors.newCachedThreadPool();

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

        assertEquals(1000, SynchronizedMethods.staticSum);
    }

Synchronized Blocks

Sometimes, we may not need to Synchronize the whole method, but a part of it, when we can use Synchronized Blocks.

1
2
3
4
5
    public void performSynchronizedTask() {
        synchronized (this) {
            setSum(getSum() + 1);
        }
    }

Let’s see how to test.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
    @Test
    public void givenMultiThread_whenBlockSync() throws InterruptedException {
        ExecutorService service = Executors.newFixedThreadPool(3);
        SynchronizedMethods synchronizedBlocks = new SynchronizedMethods();

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

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

Above we synchronize the instance, if in a static method we can also synchronize the class.

1
2
3
4
5
    public static void performStaticSyncTask(){
        synchronized (SynchronizedMethods.class) {
            staticSum = staticSum + 1;
        }
    }

Let’s see how to test.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
    @Test
    public void givenMultiThread_whenStaticSyncBlock() throws InterruptedException {
        ExecutorService service = Executors.newCachedThreadPool();

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

        assertEquals(1000, SynchronizedMethods.staticSum);