Synchronization in Java

When multiple threads access shared data, race conditions can occur, leading to inconsistent and incorrect results. To prevent this, Java provides a mechanism called synchronization, which ensures that only one thread can access a critical section at a time.

Synchronization is primarily achieved using the synchronized keyword, which relies on an internal locking mechanism known as intrinsic locks or monitor locks.

The synchronized Keyword

The synchronized keyword is used to control access to a block of code or method so that only one thread can execute it at a time for a given object.

When a thread enters a synchronized section, it acquires a lock. Other threads attempting to enter the same section must wait until the lock is released.
class Counter {
    private int count = 0;

    public synchronized void increment() {
        count++;
    }
}
In this example, only one thread can execute the increment() method at a time for the same Counter object.

How synchronized Works

Every object in Java has an associated intrinsic lock. When a thread enters a synchronized method or block, it acquires this lock.

- If the lock is available โ†’ thread proceeds
- If the lock is held โ†’ thread enters BLOCKED state
- Once the thread exits the synchronized section, the lock is released automatically.

Method-Level Synchronization

When you declare a method as synchronized, the lock is acquired on the entire object (this).
class Counter {
    private int count = 0;

    public synchronized void increment() {
        count++;
    }
}
Characteristics:
- Locks the entire method
- Simple and easy to use
- May reduce performance if overused

Block-Level Synchronization

Instead of locking the entire method, you can synchronize only a specific block of code. This provides more control and better performance.
class Counter {
    private int count = 0;

    public void increment() {
        synchronized (this) {
            count++;
        }
    }
}
Here, only the critical section is locked, not the entire method.
Advantages:
- Better performance (smaller locked region)
- More fine-grained control
- Allows different locks for different resources
Example with custom lock object:
Using a private lock object instead of this improves encapsulation, because no external code can access or interfere with this lock. This prevents unintended blocking or synchronization issues caused by outside components.
class Counter {
    private int count = 0;
    private final Object lock = new Object();

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

Method vs Block-Level Synchronization

Method-Level:
- Locks entire method
- Easier to implement
- Less flexible
Block-Level:
- Locks only specific code
- More efficient
- Preferred in real-world applications

In practice, block-level synchronization is generally preferred for better performance and scalability.

Intrinsic Locks (Monitor Locks)

Java uses intrinsic locks, also known as monitor locks, to implement synchronization.

Every object has a built-in lock that can be used for synchronization. When a thread enters a synchronized block or method, it acquires the object's monitor lock.
        Object object = new Object();
        synchronized (object) {
            // thread holds object's monitor lock 
            System.out.println("Inside synchronized block");
        }
Key Properties of Intrinsic Locks:
- Each object has one monitor lock
- Only one thread can hold the lock at a time
- Locks are reentrant (same thread can acquire multiple times)
- Automatically released when exiting synchronized block

Reentrancy Example

class Example {
    public synchronized void methodA() {
        methodB();
    }

    public synchronized void methodB() {
        System.out.println("Reentrant lock acquired");
    }
} 
Here, the same thread can enter methodB() even though it already holds the lock from methodA().

Synchronization is a fundamental concept in Java concurrency that ensures safe access to shared resources. By using the synchronized keyword, developers can protect critical sections and prevent race conditions.

Understanding the difference between method-level and block-level synchronization, along with the role of intrinsic locks, is essential for writing efficient and scalable multithreaded applications.

In the next chapter, we will dive deeper into volatile keyword and explore how memory visibility plays a critical role in concurrent programming.
Nagesh Chauhan
Nagesh Chauhan
Principal Engineer | Java ยท Spring Boot ยท Python ยท Microservices ยท AI/ML

Principal Engineer with 14+ years of experience in designing scalable systems using Java, Spring Boot, and Python. Specialized in microservices architecture, system design, and machine learning.

Share this Article

๐Ÿ’ฌ Comments

Join the discussion