What’s synchronization in Java? How does it work?

Vivek Bansal
5 min readJun 20, 2023

--

This article will help you understand the following things:

1. What is the problem?
2. What’s synchronization in Java?
3. Why use synchronization?
4. How to use synchronization?
5. How does it work internally?
6. Performance?

What is the problem?

Let’s imagine that we have a bridge that has a maximum capacity of one person at a time due to structural limitations. Crossing the bridge with more than one person simultaneously can result in the bridge collapsing and causing accidents. We need to design a mechanism to ensure that only one person crosses the bridge at any given time, maintaining safety and preventing overcrowding.

Now consider,
Bridge = Shared Resource
Person = Thread

In computer terminology, the problem statement revolves around ensuring safe concurrent access and modification of a shared resource by multiple threads, mitigating the risk of data corruption.

What’s Synchronization in Java?

Synchronization in Java ensures that only one thread can access a shared resource or critical section at a time, preventing data corruption and inconsistencies.

This is usually achieved by following the below steps:
1. New thread comes and acquires a lock on the shared object/class. The thread performs the required operation while the other incoming threads patiently wait for their turn.
2. The thread releases the acquired lock.
3. Same steps start from step 1 for other threads.

Example: Imagine a scenario where multiple people need to take turns using a printer in an office. Synchronization in Java is like a system that ensures only one person can use the printer at a time, preventing conflicts and ensuring that everyone’s print jobs are processed correctly and without interference.

Why use synchronization?

If multiple threads try to simultaneously access and modify the shared data, it can lead to data corruption. Inconsistent or incorrect values can be read or written, causing unexpected behaviour and incorrect program output.

As discussed in the above example, if multiple people try to access the printer simultaneously without synchronization, they might end up sending their print jobs at the same time. This can lead to conflicts where print jobs get mixed up, pages are printed out of order, or multiple print jobs are overlapped on the same paper.

How to use synchronization?

At a high level, there are two ways by which you can achieve synchronization using the synchronized keyword:

  • Synchronized Methods: When a thread invokes a synchronized method, it acquires the lock associated with the object instance on which the method is called. Other threads trying to access synchronized methods on the same object instance will be blocked until the lock is released.
public synchronized void synchronizedMethod() {
// Code that requires synchronization
}
  • Synchronized Blocks: You can use synchronized blocks to enclose a specific section of code that needs to be synchronized. A synchronized block is defined using the synchronized keyword followed by the object instance or class used for synchronization. Only one thread can execute the code within a synchronized block at a time.
public void someMethod() {
// Code executed without synchronization

synchronized (sharedObject) {
// Code that requires synchronization
}

// Code executed without synchronization
}

How does it work internally?

Before deep diving into the internal implementation, let me first explain the concept of Java Monitor.

Every Java object is associated with a Java monitor and is implemented using the synchronized keyword. A monitor ensures that only one thread can execute a synchronized block of code or method on an object at a time. When a thread enters a synchronized block, it acquires the monitor associated with the object, and other threads attempting to enter the same or different block will be blocked until the first thread releases the monitor.

PS: There’s a difference between Java Locks and Java Monitor. Both of them are different mechanisms to provide synchronization. Read Performance section to understand more on this.

Now, Consider the following example for understanding the internal working of the synchronized keyword:

public class Counter {
private int count = 0;

public synchronized void increment() {
count++;
}

public synchronized void decrement() {
count--;
}

public synchronized int getCount() {
return count;
}
}

In this example, we have a class named Counter that maintains an internal count variable. The increment, decrement, and getCount methods are all marked as synchronized(only considered for explaining the example).

  1. Multiple threads can operate concurrently on a single instance of the Counter class.
  2. When a thread calls the increment method on a Counter instance, it attempts to acquire the lock associated with that instance's monitor.
  3. If the lock is available, the thread acquires the lock, enters the monitor, and executes the critical section of the increment method, which increments the count variable by 1.
  4. While a thread is inside the synchronized increment method, other threads that try to call either the increment, decrement, or getCount methods on the same Counter instance are blocked, as they need to acquire the lock associated with the monitor.
  5. Once the thread finishes executing the critical section (i.e., incrementing the count), it releases the lock, allowing other waiting threads to acquire the lock and execute their synchronized methods.
  6. Similarly, when threads call the synchronized decrement or getCount methods, they follow the same steps of acquiring and releasing the lock associated with the monitor, ensuring that only one thread executes the critical section at a time.

Bonus

If you try to find the synchronized keyword implementation inside the Java library or packages, you won’t find it.

The implementation of the synchronized keyword is an implementation detail of the Java virtual machine (JVM). The JVM is responsible for managing threads and synchronization, and the synchronized keyword is just a way for Java programmers to interact with the JVM’s synchronization mechanisms. The implementation of the synchronized keyword is not part of the Java API or library. Therefore, the actual working of the synchronized keyword depends on the JVM version used for development.

Performance

As you would have guessed by now, using synchronized blocks and methods leads to performance overhead. Gaining a lock for executing a method blocks all other threads which might want to execute other synchronized methods. Thus, in scenarios where finer-grained control is needed or more advanced synchronization mechanisms are required, Java provides other options like Lock implementations from the java.util.concurrent.locks package, such as ReentrantLock, ReadWriteLock, etc.

Example:

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
class Counter {
private int count = 0;
private Lock lock = new ReentrantLock();
public void increment() {
lock.lock();
try {
count++;
} finally {
lock.unlock();
}
}
}

Thus, achieving a locking mechanism using the package java.util.concurrent.locks is much more efficient than using the synchronized keyword.

I hope this article helped you learn something new!🙃

If you ❤️ this article, consider giving it a clap so that it reaches to the wider audience. Follow me on Linkedin | Twitter where I keep sharing content that you may found useful.

Some External resources:
- Synchronization
- Java Monitor vs Java Lock
- Package java.util.concurrent.locks

--

--

Vivek Bansal

I write anything and everything around Software Engineering