0
0
JavaComparisonIntermediate · 4 min read

Synchronized vs ReentrantLock in Java: Key Differences and Usage

In Java, synchronized is a built-in keyword that provides simple mutual exclusion with automatic lock release, while ReentrantLock is a flexible class from java.util.concurrent.locks offering advanced features like timed waits and interruptible lock acquisition. ReentrantLock allows more control but requires manual unlocking, unlike synchronized which is easier to use but less flexible.
⚖️

Quick Comparison

This table summarizes the main differences between synchronized and ReentrantLock in Java.

FeaturesynchronizedReentrantLock
TypeKeywordClass from java.util.concurrent.locks
Lock ReleaseAutomatic (block exit)Manual (must call unlock())
Lock AcquisitionNon-interruptibleSupports interruptible and timed lock attempts
FairnessNo fairness optionCan be fair (first-come-first-served)
Condition SupportNo explicit condition variablesSupports multiple Condition objects
PerformanceSimpler, less overheadMore flexible, slightly more overhead
⚖️

Key Differences

synchronized is a simple and easy-to-use keyword that locks a block or method and automatically releases the lock when the block finishes or an exception occurs. It does not support interruptible lock acquisition or fairness policies, making it less flexible but safe and straightforward for basic synchronization needs.

ReentrantLock is a class that provides explicit lock control with methods like lock(), unlock(), tryLock(), and supports interruptible locking and timed waits. It also allows fairness policies to avoid thread starvation and supports multiple Condition objects for advanced thread coordination.

Because ReentrantLock requires manual unlocking, it demands careful coding to avoid deadlocks or forgotten unlocks, while synchronized handles this automatically. The choice depends on whether you need advanced features or prefer simplicity.

⚖️

Code Comparison

Here is an example showing how to use synchronized to protect a shared counter increment.

java
public class Counter {
    private int count = 0;

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

    public synchronized int getCount() {
        return count;
    }

    public static void main(String[] args) throws InterruptedException {
        Counter counter = new Counter();

        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                counter.increment();
            }
        });

        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                counter.increment();
            }
        });

        t1.start();
        t2.start();
        t1.join();
        t2.join();

        System.out.println("Final count: " + counter.getCount());
    }
}
Output
Final count: 2000
↔️

ReentrantLock Equivalent

This example shows the same counter increment using ReentrantLock for locking.

java
import java.util.concurrent.locks.ReentrantLock;

public class Counter {
    private int count = 0;
    private final ReentrantLock lock = new ReentrantLock();

    public void increment() {
        lock.lock();
        try {
            count++;
        } finally {
            lock.unlock();
        }
    }

    public int getCount() {
        lock.lock();
        try {
            return count;
        } finally {
            lock.unlock();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Counter counter = new Counter();

        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                counter.increment();
            }
        });

        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                counter.increment();
            }
        });

        t1.start();
        t2.start();
        t1.join();
        t2.join();

        System.out.println("Final count: " + counter.getCount());
    }
}
Output
Final count: 2000
🎯

When to Use Which

Choose synchronized when you want simple, safe locking with automatic release and do not need advanced features like timed or interruptible locks. It is perfect for straightforward synchronization in most cases.

Choose ReentrantLock when you need more control over locking, such as fairness policies, interruptible lock acquisition, timed waits, or multiple condition variables for complex thread coordination. It is ideal for advanced concurrency scenarios where flexibility is required.

Key Takeaways

synchronized is simpler and automatically releases locks, best for basic synchronization.
ReentrantLock offers advanced features like timed, interruptible locks and fairness control.
Always manually unlock ReentrantLock in a finally block to avoid deadlocks.
Use synchronized for ease and safety; use ReentrantLock for flexibility and advanced control.