Understand ReentrantLock: How It Works and How It Differs from synchronized

ReentrantLock is a powerful synchronization mechanism in Java, part of the java.util.concurrent.locks package. Compared with the synchronized keyword, ReentrantLock offers advanced capabilities and greater flexibility, making it suitable for complex concurrency control scenarios.

ReentrantLock vs. synchronized

1. Performance Comparison

Since JDK 6, the performance difference between ReentrantLock and synchronized has largely diminished due to JVM-level optimizations such as biased locking and lightweight locking.

Update (JDK 17+ / 21): With the deprecation of biased locking (JEP 374), synchronized now performs better than ReentrantLock in most general-use cases. For simple mutual exclusion, prefer synchronized for its cleaner syntax and built-in lock management.

2. Feature Comparison

Feature synchronized ReentrantLock
Interruptible lock acquisition No Yes
Timeout for acquiring lock No Yes
Fairness control No Yes (optional)
Multiple condition variables No (only wait/notify) Yes (newCondition())
Reentrancy Yes Yes
Automatic lock release Yes (via block exit) No (must call unlock())

ReentrantLock remains essential when fine-grained lock control, fairness, or advanced waiting mechanisms are needed.

How to Use ReentrantLock

Lock lock = new ReentrantLock(); // Non-fair lock by default
lock.lock();
try {
    // Critical section
} finally {
    lock.unlock();
}

Fair Lock Example

Lock fairLock = new ReentrantLock(true); // Explicitly use a fair lock

ReentrantLock Source Code Analysis (Based on JDK 21)

Class Structure Overview

public class ReentrantLock implements Lock, java.io.Serializable {
    private final Sync sync;

    abstract static class Sync extends AbstractQueuedSynchronizer {}
    static final class NonfairSync extends Sync {}
    static final class FairSync extends Sync {}
}

Internally, ReentrantLock uses the powerful AQS (AbstractQueuedSynchronizer) framework to manage thread queuing and state.

Lock Acquisition (lock())

Nonfair Lock

final void lock() {
    if (compareAndSetState(0, 1))
        setExclusiveOwnerThread(Thread.currentThread());
    else
        acquire(1);
}

Fair Lock

final void lock() {
    acquire(1); // Always enqueues the thread to maintain fairness
}

Fair locks check hasQueuedPredecessors() to determine if the thread should wait in line, ensuring first-come-first-served behavior.

tryAcquire() Logic

final boolean nonfairTryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();
    if (c == 0) {
        if (compareAndSetState(0, acquires)) {
            setExclusiveOwnerThread(current);
            return true;
        }
    } else if (current == getExclusiveOwnerThread()) {
        int nextc = c + acquires;
        if (nextc < 0)
            throw new Error("Maximum lock count exceeded");
        setState(nextc);
        return true;
    }
    return false;
}

Lock Release (unlock())

public final boolean release(int arg) {
    if (tryRelease(arg)) {
        Node h = head;
        if (h != null && h.waitStatus != 0)
            unparkSuccessor(h);
        return true;
    }
    return false;
}

tryRelease

protected final boolean tryRelease(int releases) {
    int c = getState() - releases;
    if (Thread.currentThread() != getExclusiveOwnerThread())
        throw new IllegalMonitorStateException();
    boolean free = false;
    if (c == 0) {
        free = true;
        setExclusiveOwnerThread(null);
    }
    setState(c);
    return free;
}

unparkSuccessor

private void unparkSuccessor(Node node) {
    int ws = node.waitStatus;
    if (ws < 0)
        compareAndSetWaitStatus(node, ws, 0);

    Node s = node.next;
    if (s == null || s.waitStatus > 0) {
        s = null;
        for (Node t = tail; t != null && t != node; t = t.prev)
            if (t.waitStatus <= 0)
                s = t;
    }

    if (s != null)
        LockSupport.unpark(s.thread);
}

Summary & Best Practices

When to Use What?

synchronized continues to receive JVM-level optimizations and is recommended for general locking needs. ReentrantLock shines in advanced scenarios.