ReentrantLock source code analysis

Concept:

ReentrantLock is a reentrant lock that supports fair locks and unfair locks. The underlying implementation is based on AQS.

Usage scenarios:

It is more flexible and lightweight to use than Synchronized.

ReentrantLock Synchronized
Usage location Code block Method, code block
Implementation mechanism AQS Monitor (monitor)
Release form Explicit call, explicit release Automatic release
Lock type Fair lock, unfair lock Unfair lock
Reentrancy Reentrancy Reentrancy
Basic usage:
/**
 * @author maoyouhua
 * @version jdk21
 */
public class AqsDemo {
    private final ReentrantLock lock = new ReentrantLock();
    public void write1(){
        System.out.println("Enter write1...");
        lock.lock();
        try {
            System.out.println(Thread.currentThread().getName() + "write1 ... aqs");
            TimeUnit.SECONDS.sleep(10 * 60);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        } finally {
            lock.unlock();
            System.out.println("Release write1...");
        }
    }
    public void write2(){
        System.out.println("Enter write2...");
        lock.lock();
        try {
            System.out.println(Thread.currentThread().getName() + "write2 ... aqs");
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        } finally {
            lock.unlock();
            System.out.println("Release write2...");
        }
    }
    public static void main(String[] args) {

        AqsDemo aqsDemo = new AqsDemo();
        new Thread(aqsDemo::write1,"Thread - write - 1").start();
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        new Thread(aqsDemo::write2,"Thread - write - 2").start();
    }
}
Source code analysis:

ReentrantLock implements the Lock interface and has two important methods lock() and unlock();

 public ReentrantLock() {
        sync = new NonfairSync();
    }
    public ReentrantLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
    }

The constructor supports the creation of fair locks and unfair locks. The default is unfair locks. Unfair locks can reduce resources wasted by thread switching and help improve execution efficiency.

It can be seen that FairSync and NonfairSync inherit from Sync and implement logic based on AbstractQueuedSynchronizer.

AbstractQueuedSynchronizer internally maintains a CLH Nodes queue and lock status flag state to complete collaboration.

/**
* @author maoyouhua
* @version jdk21
*/
//Lock status flag bit
private volatile int state;

// CLH Nodes
abstract static class Node {
        volatile Node prev; // initially attached via casTail
        volatile Node next; // visibly nonnull when signallable
        Thread waiter; // visibly nonnull when enqueued
        volatile int status; // written by owner, atomic bit ops by others

        // methods for atomic operations
        final boolean casPrev(Node c, Node v) { // for cleanQueue
            return U.weakCompareAndSetReference(this, PREV, c, v);
        }
        final boolean casNext(Node c, Node v) { // for cleanQueue
            return U.weakCompareAndSetReference(this, NEXT, c, v);
        }
        final int getAndUnsetStatus(int v) { // for signaling
            return U.getAndBitwiseAndInt(this, STATUS, ~v);
        }
        final void setPrevRelaxed(Node p) { // for off-queue assignment
            U.putReference(this, PREV, p);
        }
        final void setStatusRelaxed(int s) { // for off-queue assignment
            U.putInt(this, STATUS, s);
        }
        final void clearStatus() { // for reducing unneeded signals
            U.putIntOpaque(this, STATUS, 0);
        }

        private static final long STATUS
            = U.objectFieldOffset(Node.class, "status");
        private static final long NEXT
            = U.objectFieldOffset(Node.class, "next");
        private static final long PREV
            = U.objectFieldOffset(Node.class, "prev");
    }
lock():
 /**
     * @author maoyouhua
     * @version jdk21
     */
    // ReentrantLock
    public void lock() {
        //The implementation of the lock method is the lock method of the inner class Sync
        sync.lock();
    }

    // Sync
    final void lock() {
        //Initial attempt to acquire the lock
        if (!initialTryLock())
            acquire(1);
        }
/**
 * @author maoyouhua
 * @version jdk21
 */
// NonfairSync
final boolean initialTryLock() {
            Thread current = Thread.currentThread();
            // When acquiring the lock for the first time, directly set the lock status to 1 and set the occupying thread to the current thread.
            if (compareAndSetState(0, 1)) {
                setExclusiveOwnerThread(current);
                return true;
            }
            // If the value exchange fails, determine whether the current thread is the occupying thread. If so, add one to the status value.
            //Here is the key to the reentrant lock
            else if (getExclusiveOwnerThread() == current) {
                int c = getState() + 1;
                if (c < 0) // overflow
                    throw new Error("Maximum lock count exceeded");
                setState(c);
                return true;
            // None of the previous conditions are met and return false
            } else
                return false;
        }
/**
 * @author maoyouhua
 * @version jdk21
 */
// FairSync
final boolean initialTryLock() {
            Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
                //Fair lock needs to be queued
                if (!hasQueuedThreads() & amp; & amp; compareAndSetState(0, 1)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            } else if (getExclusiveOwnerThread() == current) {
                if ( + + c < 0) // overflow
                    throw new Error("Maximum lock count exceeded");
                setState(c);
                return true;
            }
            return false;
        }
/**
 * @author maoyouhua
 * @version jdk21
 */
// queue    
public final boolean hasQueuedThreads() {
        for (Node p = tail, h = head; p != h & amp; & amp; p != null; p = p.prev)
            if (p.status >= 0)
                return true;
        return false;
    }
/**
 * @author maoyouhua
 * @version jdk21
 */
// AbstractQueuedSynchronizer
public final void acquire(int arg) {
        //Try to acquire the lock again
        if (!tryAcquire(arg))
            // If acquisition fails, go to acquire
            acquire(null, arg, false, false, false, 0L);
}
/**
 * @author maoyouhua
 * @version jdk21
 */
// NonfairSync
protected final boolean tryAcquire(int acquires) {
            //Acquire the lock again. The lock status is 0, which means the lock is not occupied. Use CAS to acquire the lock again, and set the occupying thread to the current thread.
            if (getState() == 0 & amp; & amp; compareAndSetState(0, acquires)) {
                setExclusiveOwnerThread(Thread.currentThread());
                return true;
            }
            //return false if acquisition fails
            return false;
        }
/**
 * @author maoyouhua
 * @version jdk21
 */
// AbstractQueuedSynchronizer
final int acquire(Node node, int arg, boolean shared,
                      boolean interruptible, boolean timed, long time) {
        Thread current = Thread.currentThread();
        byte spins = 0, postSpins = 0; // retries upon unpark of first thread
        boolean interrupted = false, first = false;
        Node pred = null; // predecessor of node when enqueued

        /*
         * Repeatedly:
         * Check if node now first
         * if so, ensure head stable, else ensure valid predecessor
         * if node is first or not yet enqueued, try acquiring
         * else if queue is not initialized, do so by attaching new header node
         * resort to spinwait on OOME trying to create node
         * else if node not yet created, create it
         * resort to spinwait on OOME trying to create node
         * else if not yet enqueued, try once to enqueue
         * else if woken from park, retry (up to postSpins times)
         * else if WAITING status not set, set and retry
         * else park and clear WAITING status, and check cancellation
         */

        for (;;) {
            if (!first & amp; & amp; (pred = (node == null) ? null : node.prev) != null & amp; & amp;
                !(first = (head == pred))) {
                if (pred.status < 0) {
                    cleanQueue(); // predecessor canceled
                    continue;
                } else if (pred.prev == null) {
                    Thread.onSpinWait(); // ensure serialization
                    continue;
                }
            }
            if (first || pred == null) {
                // 1. Try tryAcquire
                // 3. Try tryAcquire again
                // 5. Try again tryAcquire
                // 7. Try again tryAcquire
                // 9. Try again tryAcquire again
                // 11. Try tryAcquire again and again, and obtain the lock successfully.
                boolean acquired;
                try {
                    if (shared)
                        acquired = (tryAcquireShared(arg) >= 0);
                    else
                        acquired = tryAcquire(arg);
                } catch (Throwable ex) {
                    cancelAcquire(node, interrupted, false);
                    throw ex;
                }
                // 12. Clean up the front node, set the current node as the head node, and set the sentinel node
                if (acquired) {
                    if (first) {
                        node.prev = null;
                        head = node;
                        pred.next = null;
                        node.waiter = null;
                        if (shared)
                            signalNextIfShared(node);
                        if (interrupted)
                            current.interrupt();
                    }
                    return 1;
                }
            }
            Node t;
            // 2. If there is no tail node, initialize the head and tail nodes, which are called sentinel nodes.
            if ((t = tail) == null) { // initialize queue
                if (tryInitializeHead() == null)
                    return acquireOnOOME(shared, arg);
            // 4. If the node is null, create a node of SharedNode type.
            } else if (node == null) { // allocate; retry before enqueue
                try {
                    node = (shared) ? new SharedNode() : new ExclusiveNode();
                } catch (OutOfMemoryError oome) {
                    return acquireOnOOME(shared, arg);
                }
            // 6. The front node is empty, ready to join the queue, and set the front node and thread
            } else if (pred == null) { // try to enqueue
                node.waiter = current;
                node.setPrevRelaxed(t); // avoid unnecessary fence
                if (!casTail(t, node))
                    node.setPrevRelaxed(null); // back out
                else
                    t.next = node;
            } else if (first & amp; & amp; spins != 0) {
                --spins; // reduce unfairness on rewaits
                Thread.onSpinWait();
            // 8. Set the current thread status to WATTING = 1
            } else if (node.status == 0) {
                node.status = WAITING; // enable signal and recheck
            } else {
                long nanos;
                spins = postSpins = (byte)((postSpins << 1) | 1);
                if (!timed)
                    // 10. Block the current thread and release the pass after obtaining the pass.
                    LockSupport.park(this);
                else if ((nanos = time - System.nanoTime()) > 0L)
                    LockSupport.parkNanos(this, nanos);
                else
                    break;
                node.clearStatus();
                if ((interrupted |= Thread.interrupted()) & amp; & amp; interruptible)
                    break;
            }
        }
        return cancelAcquire(node, interrupted, interruptible);
    }
/**
 * @author maoyouhua
 * @version jdk21
 */
// AbstractQueuedSynchronizer
private Node tryInitializeHead() {
        for (Node h = null, t;;) {
            if ((t = tail) != null)
                return t;
            else if (head != null)
                Thread.onSpinWait();
            else {
                if (h == null) {
                    try {
                        h = new ExclusiveNode();
                    } catch (OutOfMemoryError oome) {
                        return null;
                    }
                }
                if (U.compareAndSetReference(this, HEAD, null, h))
                    return tail = h;
            }
        }
    }
/**
 * @author maoyouhua
 * @version jdk21
 */
 // AbstractQueuedSynchronizer
 //Cancel lock acquisition
 private int cancelAcquire(Node node, boolean interrupted,
                              boolean interruptible) {
        if (node != null) {
            node.waiter = null;
            node.status = CANCELLED;
            if (node.prev != null)
                cleanQueue();
        }
        if (interrupted) {
            if(interruptible)
                return CANCELLED;
            else
                Thread.currentThread().interrupt();
        }
        return 0;
    }
unlock():
/**
 * @author maoyouhua
 * @version jdk21
 */
// ReentrantLock
public void unlock() {
        sync.release(1);
    }
// AbstractQueuedSynchronizer
public final boolean release(int arg) {
        if (tryRelease(arg)) {
            signalNext(head);
            return true;
        }
        return false;
    }
/**
 * @author maoyouhua
 * @version jdk21
 */
// ReentrantLock
//Clear the thread holding the lock, and set the lock status value minus one
protected final boolean tryRelease(int releases) {
    int c = getState() - releases;
    if (getExclusiveOwnerThread() != Thread.currentThread())
        throw new IllegalMonitorStateException();
    boolean free = (c == 0);
    if (free)
        setExclusiveOwnerThread(null);
    setState(c);
    return free;
}
/**
 * @author maoyouhua
 * @version jdk21
 */
//Give the next queued thread a pass
private static void signalNext(Node h) {
        Node s;
        if (h != null & amp; & amp; (s = h.next) != null & amp; & amp; s.status != 0) {
            s.getAndUnsetStatus(WAITING);
            LockSupport.unpark(s.waiter);
        }
    }

The knowledge points of the article match the official knowledge files, and you can further learn related knowledge. Java Skill TreeHomepageOverview 138932 people are learning the system