Fundamentals 10 min read

How Does Java’s ReentrantLock Work? Inside Fair vs. Non‑Fair Locks

This article explains the inner workings of Java's ReentrantLock, covering its API, the difference between fair and non‑fair modes, the lock acquisition and release algorithms, and how it compares to the built‑in synchronized keyword, complete with code examples and detailed analysis.

Programmer DD
Programmer DD
Programmer DD
How Does Java’s ReentrantLock Work? Inside Fair vs. Non‑Fair Locks

Introduction

ReentrantLock is a re‑entrant mutual exclusion lock that offers the same basic semantics as the synchronized statement but with more powerful and flexible features, reducing the likelihood of deadlocks.

API Overview

A re‑entrant mutex lock that behaves like the implicit monitor lock used by synchronized , yet provides additional capabilities such as fairness control.

The lock is owned by the thread that most recently acquired it and has not yet released it. Methods like isHeldByCurrentThread() and getHoldCount() let you inspect the lock state.

1. Acquiring the Lock

Typical usage:

public void lock() {
    sync.lock();
}

The internal Sync class extends AbstractQueuedSynchronizer (AQS) and has two subclasses: FairSync (fair lock) and NonfairSync (default non‑fair lock).

For the non‑fair lock, the lock() method first tries a fast CAS acquisition; if it fails, it delegates to AQS.acquire(int) which may block the thread.

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

The tryAcquire(int acquires) implementation for the non‑fair lock looks like this:

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

This method first checks whether the lock state is zero (unlocked). If so, it attempts a CAS to set the state to the requested acquire count. If the current thread already holds the lock, it increments the hold count, allowing re‑entrancy.

2. Releasing the Lock

After the critical section, the lock is released via:

public void unlock() {
    sync.release(1);
}

The release(int arg) method in AQS calls tryRelease(int):

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;
}

The lock is fully released only when the hold count reaches zero; then the owner thread is cleared and waiting threads may acquire the lock.

3. Fair vs. Non‑Fair Locks

The only difference between the two modes is the additional fairness check in the fair lock’s tryAcquire(int) method:

protected final boolean tryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();
    if (c == 0) {
        if (!hasQueuedPredecessors() && 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;
}

The method hasQueuedPredecessors() returns true if there are waiting threads ahead of the current thread in the FIFO queue, ensuring first‑come‑first‑served acquisition.

4. ReentrantLock vs. synchronized

Both provide the same memory semantics.

ReentrantLock offers additional features: timed try‑lock, interruptible lock acquisition, and multiple Condition objects for finer‑grained waiting.

It allows non‑blocking lock attempts (polling) and can be more performant under high contention.

Lock release must be performed in a finally block to avoid resource leaks.

ReentrantLock supports interrupt handling, which synchronized does not.

Overall, ReentrantLock gives developers more control and flexibility compared to the built‑in synchronized construct.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

fair locknonfair-lock
Programmer DD
Written by

Programmer DD

A tinkering programmer and author of "Spring Cloud Microservices in Action"

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.