Java - Synchronized block

The synchronized keyword prevents a race condition from occurring when two or more threads access a variable at the same time in a multi-threaded environment.

If a code block that can cause a race condition is wrapped with the synchronized keyword, only one thread can enter this code block. Other threads wait for the first thread to exit this code block to prevent race condition from occurring.

This article will look at how to use synchronized and how to use it efficiently.

Why use the synchronized keyword?

If you look at the following code, you are creating a thread pool and increasing the number of number variable in multiple threads.

public class SynchronizedKeywordExample {
    static long number = 0;

    public static void main(String args[]) {
        ExecutorService service = Executors.newCachedThreadPool();

        for (int i = 0; i < 1000; i++) {
            service.submit(() -> {
                number++;
                System.out.println(number);
            });
        }
    }
}

Running the above code produces the following output: The numbers are increasing sequentially at first, but not sequentially later. In fact, since a single line of Java code is translated into multiple lines of machine code, there is no guarantee that the code will be executed sequentially in a multi-threaded environment.

1
2
3
4
....
15
16
17
19
20
18
21
....

Let`s wrap the part where a race condition can occur in the code above with the synchronized keyword. A variable that can be accessed simultaneously by multiple threads is number. Just wrap this in a synchronized block.

public class SynchronizedKeywordExample {
    static long number = 0;

    public static void main(String args[]) {
        ExecutorService service = Executors.newCachedThreadPool();

        for (int i = 0; i < 1000; i++) {
            service.submit(() -> {
                synchronized (SynchronizedKeywordExample.class) {
                    number++;
                    System.out.println(number);
                }
            });
        }
    }
}

Now, if you run the above code, you can see that the number increases sequentially as follows. Since only one thread can enter a synchronized block, after one thread changes the value, the next thread can change the value. I was able to make it behave as intended by preventing the race condition from occurring.

1
2
3
4
.....
14
15
16
17
18
19
20
.....

In general, using the synchronized keyword, you can synchronize in the following pattern:

  • Instance Method Synchronization
  • static method synchronization
  • Synchronize within a method

Synchronize instance methods

Synchronization can be done on an instance-by-instance basis using the synchronized keyword.

I created a Counter class as follows, and will call increase() in multithreading.

public class Counter {
    private long number = 0;

    public void increase() {
        number++;
        System.out.println(number);
    }
}

If you make the multithread call increase() as shown below, a race condition may occur and cause a problem.

ExecutorService service = Executors.newCachedThreadPool();
Counter counter = new Counter();

for (int i = 0; i < 1000; i++) {
    service.submit(() -> {
        counter.increase();
    });
}

Synchronization can be applied on a per-method basis by adding the synchronized keyword to a method as follows: That is, only one thread can enter this method. Other threads will wait until the method call is finished in the thread that entered first.

public class Counter {
    private long number = 0;

    public synchronized void increase() {
        number++;
        System.out.println(number);
    }
}

It should be noted that synchronized is not synchronized in units of specific methods, but in units of objects. For example, when synchronized is added to increase() and decrease() of class AAA as shown in the code below, these two methods cannot be executed at the same time. Synchronization set in a method is synchronized on an instance-by-instance basis, so only one thread is allowed to enter the synchronized block within this object.

class AAA {
  public synchronized void increase() {
  }
  public synchronized void decrease() {
  }
}

In other words, applying synchronized to a method can be very inefficient.

Synchronize static methods

With synchronized, you can synchronize in units of static methods.

The following code adds synchronized to the static method increase().

public class Counter {
    private static long number = 0;

    public static synchronized void increase() {
        number++;
        System.out.println(number);
    }
}

There is only one static method in a class. Even if multiple objects are created, they share one static method. Therefore, two or more Counter objects are created, and when these objects call increase() at the same time, only one thread can enter this method. Thus, multiple objects of the same class can be synchronized.

Synchronize inside methods

Applying synchronized on a per-method basis can be inefficient. In the example below, it is only one variable number that triggers the race condition, because the code System.out.prinln() is also synchronized. Therefore, only the code that accesses the number with a synchronized (lock) block can be synchronized as follows. Here ‘lock’ can be thought of as a unit to be synchronized. Since this means an instance, it is synchronized on a per-object basis.

public class Counter {
    private long number = 0;

    public void increase() {
        long temp = 0;

        synchronized (this) {
            number++;
            temp = number;
        }

        System.out.println(temp);
    }
}

If synchronized is applied to increase() as follows, the synchronized block inside increase() cannot be executed while increase() is being executed. This is because they are all synchronized in units of instance objects.

public class Counter {
    private long number = 0;

    public void increase() {
        long temp = 0;

        synchronized (this) {
            number++;
            temp = number;
        }

        System.out.println(temp);
    }

    public synchronized void decrease() {
        number--;
    }
}

If you create an object called lock as follows and change the internal code of the increase method to synchronized (lock), it can be executed with decrease(). This is because decrease() synchronizes instances as a unit, and increase() synchronizes an object called lock as a unit.

public class Counter {
    private long number = 0;
    private Object lock = new Object();

    public void increase() {
        long temp = 0;

        synchronized (lock) {
            number++;
            temp = number;
        }

        System.out.println(temp);
    }

    public synchronized void decrease() {
        number--;
    }
}

As seen in the example above, when synchronizing objects that can cause race conditions, it can work efficiently by setting different synchronization units.

Clean up

The synchronized keyword is to synchronize code that may cause a race condition to ensure that only one thread can execute the code. When synchronized, the program may operate efficiently or inefficiently depending on what unit to be synchronized is set.

codechachaCopyright ©2019 codechacha