Fundamentals 23 min read

Understanding Fair and Unfair Locks in Java's ReentrantLock

This article explains the concepts, creation methods, usage examples, and internal implementation details of fair and unfair locks in Java's ReentrantLock, comparing their performance characteristics and providing guidance on when to choose each type in multithreaded applications.

Zhuanzhuan Tech
Zhuanzhuan Tech
Zhuanzhuan Tech
Understanding Fair and Unfair Locks in Java's ReentrantLock

Introduction

Java's JUC package provides the powerful ReentrantLock class, which supports both fair and unfair locking modes. Understanding the differences between these modes is essential for optimizing performance and ensuring proper resource allocation in multithreaded programs.

Fair vs. Unfair Locks

A fair lock grants lock acquisition in the order threads request it, following a first‑come‑first‑served principle. An unfair lock allows a thread to acquire the lock out of order, potentially bypassing threads that have been waiting longer.

ReentrantLock Fair and Unfair Locks

Inheritance Diagram

The ReentrantLock class implements Serializable and Lock , and defines three inner classes: Sync , NonfairSync , and FairSync . Sync extends AbstractQueuedSynchronizer (AQS) , while NonfairSync and FairSync provide the concrete implementations for unfair and fair locks respectively.

Creating Fair and Unfair Locks

ReentrantLock offers two constructors. The no‑argument constructor creates an unfair lock by default, whereas the boolean constructor creates a fair lock when true is passed and an unfair lock when false is passed.

/**
 *  No‑arg constructor
 *  Creates an unfair lock by default
 */
public ReentrantLock() {
    sync = new NonfairSync();
}

/**
 *  Constructor with fairness flag
 *  true → fair lock
 *  false → unfair lock
 */
public ReentrantLock(boolean fair) {
    sync = fair ? new FairSync() : new NonfairSync();
}

Usage Examples

Unfair Lock Example

@Test
public void testUnfairLock() throws InterruptedException {
    // Default constructor creates an unfair lock
    ReentrantLock lock = new ReentrantLock();

    for (int i = 0; i < 6; i++) {
        final int threadNum = i;
        new Thread(() -> {
            lock.lock();
            try {
                System.out.println("线程" + threadNum + "获取锁");
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
                System.out.println("线程" + threadNum + "释放锁");
            }
        }).start();
        Thread.sleep(999);
    }
    Thread.sleep(100000);
}

Typical output shows threads acquiring and releasing the lock in a non‑deterministic order.

Fair Lock Example

@Test
public void testFairLock() throws InterruptedException {
    // Pass true to create a fair lock
    ReentrantLock lock = new ReentrantLock(true);

    for (int i = 0; i < 6; i++) {
        final int threadNum = i;
        new Thread(() -> {
            lock.lock();
            try {
                System.out.println("线程" + threadNum + "获取锁");
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
                System.out.println("线程" + threadNum + "释放锁");
            }
        }).start();
        Thread.sleep(10);
    }
    Thread.sleep(100000);
}

The output demonstrates that threads acquire the lock in the order they were started, reflecting the FIFO nature of the fair lock.

Implementation Analysis

Lock Acquisition Flow

Both fair and unfair locks invoke ReentrantLock.lock() , which delegates to the underlying sync object's lock() method. The key difference lies in the concrete Sync implementation used.

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

FairSync.lock() directly calls AQS.acquire() , while NonfairSync.lock() first attempts a CAS acquisition before falling back to AQS.acquire() :

// FairSync.lock()
final void lock() {
    acquire(1);
}

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

The core AQS.acquire(int arg) method repeatedly tries tryAcquire(arg) and, if unsuccessful, enqueues the thread and invokes acquireQueued() :

public final void acquire(int arg) {
    if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}

Both FairSync and NonfairSync provide their own tryAcquire(int acquires) implementations. The fair version checks hasQueuedPredecessors() to enforce FIFO ordering, whereas the unfair version attempts a fast‑path CAS without this check.

// FairSync.tryAcquire
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;
}

// NonfairSync.tryAcquire (delegates to nonfairTryAcquire)
protected final boolean tryAcquire(int acquires) {
    return nonfairTryAcquire(acquires);
}

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

The method hasQueuedPredecessors() determines whether there are waiting threads ahead of the current one:

public final boolean hasQueuedPredecessors() {
    Node t = tail;
    Node h = head;
    Node s;
    return h != t && ((s = h.next) == null || s.thread != Thread.currentThread());
}

Lock Release Flow

ReentrantLock.unlock() delegates to sync.release(1) , which ultimately calls AQS.release(int arg) . The release process invokes tryRelease(arg) (implemented in Sync ) and, upon success, wakes up the next waiting node.

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

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

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

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

The fair lock relies on a FIFO queue to guarantee that threads acquire the lock in the order they arrived, ensuring no thread starvation but incurring higher contention and context‑switch overhead. The unfair lock allows a thread to acquire the lock immediately if it is free, reducing latency and increasing throughput at the risk of starvation under high contention.

Considerations

Choose a fair lock when strict ordering of resource access is required, such as in allocation systems where starvation must be avoided. Opt for an unfair lock in high‑throughput scenarios like caching or logging where ordering is less critical and maximum performance is desired.

About the author Kong Dezhi – Java Development Engineer at CaiHuoXia
JavaConcurrencyMultithreadingReentrantLockFairLock
Zhuanzhuan Tech
Written by

Zhuanzhuan Tech

A platform for Zhuanzhuan R&D and industry peers to learn and exchange technology, regularly sharing frontline experience and cutting‑edge topics. We welcome practical discussions and sharing; contact waterystone with any questions.

0 followers
Reader feedback

How this landed with the community

login 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.