ReentrantLock lock and release lock source code analysis

ReentrantLock is a reentrant mutex lock, which means that it can only be held by one thread at the same point in time; reentrant means that the ReentrantLock lock can be acquired multiple times by the same thread.
ReentrantLock has a distinction between fair and unfair. A FIFO waiting queue is used to manage the acquisition of the lock by all threads.
Under the “fair lock” mechanism, threads queue up to acquire locks in sequence;
The “unfair lock” will acquire the lock regardless of whether it is at the beginning of the queue when the lock is available.

1.Construction method:

 //The empty parameter constructor of ReentrantLock generates an unfair lock by default
    public ReentrantLock() {<!-- -->
        sync = new NonfairSync();
    }
    //Generate fair lock when the parameter passed in the constructor is true
    public ReentrantLock(boolean fair) {<!-- -->
        sync = fair ? new FairSync() : new NonfairSync();
    }

ReentrantLock is implemented by AQS, and the sync object in the locking code is inherited from the abstract queue synchronizer of AQS (AbstractQueuedSynchronizer).

2. Lock

2.1 Method Lock()

 //ReentrantLock locking method
    public void lock() {<!-- -->
        sync.lock();
    }
    //Lock implementation of fair lock
    final void lock() {<!-- -->
    acquire(1);
    }
    //Lock implementation of unfair lock
    final void lock() {<!-- -->
    //Use CAS for comparison and exchange. In AQS, the State value represents the status of the lock. 0 represents that the current lock has been released, and 1 represents that the lock has been acquired.
    //compareAndSetState changes the lock status from 0 to 1. If this method is successful, it means that the current thread has obtained the lock.
       if (compareAndSetState(0, 1))
       //After getting the lock, set the lock holder to be the current thread
           setExclusiveOwnerThread(Thread.currentThread());
       else
           acquire(1);
   }

2.1.1 acquire method

acquire(1) is called in both fair lock and unfair lock locking methods.

 public final void acquire(int arg) {<!-- -->
//Try to acquire the lock
        if (!tryAcquire(arg) & amp; & amp;
        //Attempt to acquire lock failed and join AQS waiting queue
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            //Interrupt thread
            selfInterrupt();
    }
2.1.1.1 tryAcquire method of fair lock
//tryAcquire of fair lock
protected final boolean tryAcquire(int acquires) {<!-- -->
//Get the current thread
    final Thread current = Thread.currentThread();
    //Get the current lock status. State 0 represents that the current lock has been released, 1 represents that the lock has been acquired once.
    int c = getState();
    //When the lock status is released
    if (c == 0) {<!-- -->
    //Get whether the current thread is waiting for the head of the queue or the waiting queue is empty
        if (!hasQueuedPredecessors() & amp; & amp;
        //Is the lock in the released state? If so, acquire the lock.
            compareAndSetState(0, acquires)) {<!-- -->
            setExclusiveOwnerThread(current);
            return true;
        }
    }
    //When the current thread is the thread holding the lock, reentrancy logic is triggered
    else if (current == getExclusiveOwnerThread()) {<!-- -->
    //Add 1 to the number of reentries
        int nextc = c + acquires;
        //When nextc is a negative number, it means that the number of reentries exceeds the maximum value of int
        if (nextc < 0)
            throw new Error("Maximum lock count exceeded");
        setState(nextc);
        return true;
    }
    //Return failed to acquire lock
    return false;
}
//True if there is a queued thread in front of the current thread; false if the current thread is at the head of the queue or the queue is empty
    public final boolean hasQueuedPredecessors() {<!-- -->
        Node t = tail; //The tail node of the waiting queue in AQS
        Node h = head;//The head node of the waiting queue in AQS
        Node s;
        //The head is equal to the tail, indicating that the queue is empty
        return h != t & amp; & amp;
        //The head node of the waiting queue in AQS is a virtual empty node
        //h.next is equal to empty, which means the queue is empty
            ((s = h.next) == null ||
            //Is the current node the first node?
             s.thread != Thread.currentThread());
    }
2.1.1.2 tryAcquire method of unfair lock

The basic logic of the tryAcquire method of unfair lock is similar to that of fair lock, except that it lacks a judgment on whether the waiting queue is empty or whether it is waiting for the head of the queue before acquiring the lock.

 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) // overflow
                throw new Error("Maximum lock count exceeded");
            setState(nextc);
            return true;
        }
        return false;
    }
2.1.1.3 Join the waiting queue after failing to acquire the lock
 private Node addWaiter(Node mode) {<!-- -->
        Node node = new Node(Thread.currentThread(), mode);
        // Try the fast path of enq; backup to full enq on failure
        Node pred = tail;
        //When the tail node is not empty
        if (pred != null) {<!-- -->
        //The current node's previous pointer points to the tail node
            node.prev = pred;
            //Use cas to point the tail node from pred to the current node
            if (compareAndSetTail(pred, node)) {<!-- -->
                pred.next = node;
                return node;
            }
        }
        //If the tail node is empty, the waiting queue has not completed initialization and the enq method is executed.
        enq(node);
        return node;
    }
    private Node enq(final Node node) {<!-- -->
        for (;;) {<!-- -->
        //In the case of multi-threading, the tail node t is not necessarily empty.
            Node t = tail;
            //The tail node is empty and the waiting queue has not completed initialization.
            if (t == null) {<!-- --> // Must initialize
            //Set the empty node as the head node
                if (compareAndSetHead(new Node()))
                //Set the head node and tail node to point to the same node
                    tail = head;
            } else {<!-- -->
           //Wait for the queue initialization to complete, set the current node as the new tail node and return
                node.prev = t;
                if (compareAndSetTail(t, node)) {<!-- -->
                    t.next = node;
                    return t;
                }
            }
        }
    }
    
    final boolean acquireQueued(final Node node, int arg) {<!-- -->
        boolean failed = true;
        try {<!-- -->
            boolean interrupted = false;
            //infinite loop
            for (;;) {<!-- -->
            //Get the previous node of the current node
                final Node p = node.predecessor();
                //The previous node is the head node, then try to acquire the lock
                if (p == head & amp; & amp; tryAcquire(arg)) {<!-- -->
                //Successfully acquire the lock, set the current node to an empty node, and set it to the head node
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    //Return without interruption
                    return interrupted;
                }
                 // Based on the change of the previous node, determine whether the current node can suspend the thread. If it can, return true.
                if (shouldParkAfterFailedAcquire(p, node) & amp; & amp;
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {<!-- -->
            if (failed)
                cancelAcquire(node);
        }
    }
    private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {<!-- -->
        int ws = pred.waitStatus;
        //The waitStatus of the previous node is -1, indicating that the current node can suspend threads
        if (ws == Node.SIGNAL)
            return true;
        if (ws > 0) {<!-- -->
         //The waitStatus of the previous node is 1, and the previous node has been cancelled. We need to look forward for nodes whose waitStatus is not 1.
            do {<!-- -->
                node.prev = pred = pred.prev;
            } while (pred.waitStatus > 0);
            pred.next = node;
        } else {<!-- -->
        //The status of the previous node is not 1 or -1, which means the node status is normal. Change the status of the previous node to -1
            compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
        }
        return false;
    }
    // Here, based on the park method of the Unsafe class, the current thread is suspended.
    private final boolean parkAndCheckInterrupt() {<!-- -->
        LockSupport.park(this);
        return Thread.interrupted();
    }

2.2 Locking method tryLock

2.2.1 tryLock()

tryLock method, both fair lock and unfair lock use the same implementation, which is the implementation of unfair lock

 public boolean tryLock() {<!-- -->
        return sync.nonfairTryAcquire(1);
    }
    //The logic is the same as the tryAcquire method implementation of the Lock method of the unfair lock
    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) // overflow
                throw new Error("Maximum lock count exceeded");
            setState(nextc);
            return true;
        }
        return false;
    }

2.2.2 tryLock(timeout,unit)

tryAcquire(1) in tryLock(timeout,unit) tries to obtain the fair and unfair implementation of lock points

 public boolean tryLock(long timeout, TimeUnit unit)
            throws InterruptedException {<!-- -->
        return sync.tryAcquireNanos(1, unit.toNanos(timeout));
    }
    public final boolean tryAcquireNanos(int arg, long nanosTimeout)
            throws InterruptedException {<!-- -->
            //Whether the thread is interrupted
        if (Thread.interrupted())
            throw new InterruptedException();
            //Try to acquire the lock
        return tryAcquire(arg) ||
        //Acquisition of lock failed,
            doAcquireNanos(arg, nanosTimeout);
    }
    
    private boolean doAcquireNanos(int arg, long nanosTimeout)
            throws InterruptedException {<!-- -->
            //If the waiting time is less than 0, give up acquiring the lock
        if (nanosTimeout <= 0L)
            return false;
         //calculating time
        final long deadline = System.nanoTime() + nanosTimeout;
        //Join the waiting queue
        final Node node = addWaiter(Node.EXCLUSIVE);
        boolean failed = true;
        try {<!-- -->
        //infinite loop
            for (;;) {<!-- -->
            //Get the previous node of the current node
                final Node p = node.predecessor();
                if (p == head & amp; & amp; tryAcquire(arg)) {<!-- -->
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return true;
                }
                nanosTimeout = deadline - System.nanoTime();
                if (nanosTimeout <= 0L)
                    return false;
                    //Hang after failure to obtain the lock
                if (shouldParkAfterFailedAcquire(p, node) & amp; & amp;
                    nanosTimeout > spinForTimeoutThreshold)
                    LockSupport.parkNanos(this, nanosTimeout);
                if (Thread.interrupted())
                    throw new InterruptedException();
            }
        } finally {<!-- -->
            if (failed)
                cancelAcquire(node);
        }
    }

tryLock(timeout,unit) tries to acquire the lock and gives up if it does not acquire it within the specified time.

2.3 Locking method lockInterruptibly()

lockInterruptibly is no different from tryLock(timeout,unit), except that it lacks the time parameter. Like the lock method, it will not give up after failing to acquire the lock, and will wait forever.

3. Release the lock

 public void unlock() {<!-- -->
        sync.release(1);
    }
    public final boolean release(int arg) {<!-- -->
        if (tryRelease(arg)) {<!-- -->
        //Release lock successfully
            Node h = head;
            //The head node is not empty, and the status of the head node is not 0, indicating that there are still nodes hanging in the waiting queue, and the wake-up method is called.
            if (h != null & amp; & amp; h.waitStatus != 0)
            //
                unparkSuccessor(h);
            return true;
        }
        return false;
    }
    
    protected final boolean tryRelease(int releases) {<!-- -->
    //Get the lock status -1
        int c = getState() - releases;
        //If the current thread does not hold the lock, an error will be reported
        if (Thread.currentThread() != getExclusiveOwnerThread())
            throw new IllegalMonitorStateException();
        boolean free = false;
        //When c equals 0, it means the lock is released cleanly
        if (c == 0) {<!-- -->
        //Release lock successfully
            free = true;
            //Set that no thread currently acquires the lock
            setExclusiveOwnerThread(null);
        }
        setState(c);
        return free;
    }
    private void unparkSuccessor(Node node) {<!-- -->
        int ws = node.waitStatus;
        if (ws < 0)
        //Change the head node status to 0
            compareAndSetWaitStatus(node, ws, 0);
        Node s = node.next;
         // If the subsequent node is null or the status of the subsequent node is 1, it means the node has been cancelled.
        if (s == null || s.waitStatus > 0) {<!-- -->
            s = null;
            //Traverse backward to obtain normal nodes with status less than 0
            for (Node t = tail; t != null & amp; & amp; t != node; t = t.prev)
                if (t.waitStatus <= 0)
                    s = t;
        }
        if (s != null)
        //Wake up the node
            LockSupport.unpark(s.thread);
    }