In-Depth Analysis of ReentrantLock Implementation and Mechanism in Java

ReentrantLock is a reentrant mutual exclusion mechanism built atop the AbstractQueuedSynchronizer (AQS) framework. It provides flexible thread synchronization in Java, supporting both fair and non-fair acquisition policies.

Structural Overview

ReentrantLock implements the Lock interface. Internally it defines a nested Sync class extending AQS, which is further specialized into UnfairSync and FairSync for non-fair and fair strategies respectively. This separation enables policy-based lock behavior while sharing core mechanics.

Comparison With Synchronized

synchronized is JVM-managed, operates on methods or blocks, and always uses a fair queuing approach. ReentrantLock offers:

  • Choice between fair and non-fair modes.
  • Explicit lock control via tryLock(timeout), lockInterruptibly().
  • Manual lock acquisition and release, requiring unlock() in a finally block.

While more powerful, ReentrantLock demands careful handling to avoid leaks or deadlocks.

Lock Acquisition Mechanics

Core Sync Abstractions

Sync declares an abstract lock() method and supplies reusalbe routines:

abstract static class Sync extends AbstractQueuedSynchronizer {
    abstract void lock();

    final boolean attemptNonFairAcquire(int units) {
        Thread cur = Thread.currentThread();
        int state = getState();
        if (state == 0) {
            if (compareAndSetState(0, units)) {
                setExclusiveOwnerThread(cur);
                return true;
            }
        } else if (cur == getExclusiveOwnerThread()) {
            int next = state + units;
            if (next < 0)
                throw new Error("Lock count overflow");
            setState(next);
            return true;
        }
        return false;
    }

    protected final boolean attemptRelease(int units) {
        int remaining = getState() - units;
        if (Thread.currentThread() != getExclusiveOwnerThread())
            throw new IllegalMonitorStateException();
        boolean fullyReleased = false;
        if (remaining == 0) {
            fullyReleased = true;
            setExclusiveOwnerThread(null);
        }
        setState(remaining);
        return fullyReleased;
    }
}

UnfairSync Strategy

UnfairSync tries immediate CAS on the state before falling back to AQS queue processing:

static final class UnfairSync extends Sync {
    final void lock() {
        if (compareAndSetState(0, 1))
            setExclusiveOwnerThread(Thread.currentThread());
        else
            acquire(1);
    }

    protected final boolean tryAcquire(int units) {
        return attemptNonFairAcquire(units);
    }
}

The attemptNonFairAcquire allows a thread to bypass the queue and grab the lock if available, ignoring waiting threads' order.

FairSync Strategy

FairSync strictly respects the FIFO order of the wait queue:

static final class FairSync extends Sync {
    final void lock() {
        acquire(1);
    }

    protected final boolean tryAcquire(int units) {
        Thread cur = Thread.currentThread();
        int state = getState();
        if (state == 0) {
            if (!hasQueuedPredecessors() && compareAndSetState(0, units)) {
                setExclusiveOwnerThread(cur);
                return true;
            }
        } else if (cur == getExclusiveOwnerThread()) {
            int next = state + units;
            if (next < 0)
                throw new Error("Lock count overflow");
            setState(next);
            return true;
        }
        return false;
    }
}

Here, hasQueuedPredecessors ensures no earlier queued thread is waiting before attempting CAS.

AQS Acquisition Pipeline

  • acquire(arg): Tries tryAcquire; on failure, enqueues the thread and may park it.
public final void acquire(int arg) {
    if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}
  • addWaiter(mode): Wraps the thread into a node and appends to tail, retrying with enq on contention.
private Node addWaiter(Node mode) {
    Node n = new Node(Thread.currentThread(), mode);
    Node prev = tail;
    if (prev != null) {
        n.setPrev(prev);
        if (compareAndSetTail(prev, n)) {
            prev.setNext(n);
            return n;
        }
    }
    enq(n);
    return n;
}
  • acquireQueued(node, arg): Loops, checking if the node is next after head to retry acquisition; otherwise parks.
final boolean acquireQueued(final Node n, int arg) {
    boolean failed = true;
    try {
        boolean interrupted = false;
        for (;;) {
            Node pred = n.getPrev();
            if (pred == head && tryAcquire(arg)) {
                setHead(n);
                pred.setNext(null);
                failed = false;
                return interrupted;
            }
            if (shouldParkAfterFailedAcquire(pred, n) && parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        if (failed) cancelAcquire(n);
    }
}
  • enq(node): Ensures node insertion evenif tail was null initially.
private Node enq(final Node n) {
    for (;;) {
        Node t = tail;
        if (t == null) {
            if (compareAndSetHead(new Node()))
                tail = head;
        } else {
            n.setPrev(t);
            if (compareAndSetTail(t, n)) {
                t.setNext(n);
                return t;
            }
        }
    }
}
  • shouldParkAfterFailedAcquire(pred, node): Adjusts predecessor's wait status to signal and decides if parking is safe.
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
    int st = pred.getWaitStatus();
    if (st == Node.SIGNAL) return true;
    if (st > 0) {
        do {
            node.setPrev(pred = pred.getPrev());
        } while (pred.getWaitStatus() > 0);
        pred.setNext(node);
    } else {
        compareAndSetWaitStatus(pred, st, Node.SIGNAL);
    }
    return false;
}

Lock Release Mechanics

Release Entrypoint

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

Successor Wake-Up

private void unparkSuccessor(Node node) {
    int st = node.getWaitStatus();
    if (st < 0) compareAndSetWaitStatus(node, st, 0);
    Node succ = node.getNext();
    if (succ == null || succ.getWaitStatus() > 0) {
        succ = null;
        for (Node t = tail; t != null && t != node; t = t.getPrev())
            if (t.getWaitStatus() <= 0) succ = t;
    }
    if (succ != null) LockSupport.unpark(succ.getThread());
}

Traversing from tail to head during wake-up avoids missing nodes due to the two-step linking process in addWaiter, ensuring all eligible waiters can be resumed correctly.

Tags: java Concurrency ReentrantLock AQS Thread Synchronization

Posted on Sun, 05 Jul 2026 16:52:08 +0000 by mligor