For ReentrantLock, the unlocking process is the same whether it is a fair lock or an unfair lock, which I have used here as an example.

    ReentrantLock lock= new ReentrantLock () ;
    lock.lock();
    lock.unlock();
Copy the code

When lock.unlock is called, the unlock begins. The specific execution class to unlock is also Sync, an inner class that inherits from AQS.

/**
* Attempts to release this lock.
*
* <p>If the current thread is the holder of this lock then the hold
* count is decremented.  If the hold count is now zero then the lock
* is released.  If the current thread is not the holder of this
* lock then {@link IllegalMonitorStateException} is thrown.
*
* @throws IllegalMonitorStateException if the current thread does not
*         hold this lock
*/
public void unlock() {
    sync.release(1);
}
Copy the code

Annotation analysis

// Try to release the lock
Attempts to release this lock.
// If the current thread is the holder of this lock, the counter is reduced.
If the current thread is the holder of this lock then the hold count is decremented.
// If the retention count is now zero, the lock is released.
If the hold count is now zero then the lock is released.
/ / if the current thread is not lock, throw {@ link IllegalMonitorStateException}.
If the current thread is not the holder of this lock then {@link IllegalMonitorStateException} is thrown.
Copy the code

If state==0, it indicates that the lock is not held by the thread. If state is greater than 0, it indicates that the lock has been re-entered several times. It is clear from the comment that an unlock method is executed once to reduce a reentrant (state-1). If the state variable is equal to 0, the lock needs to be released. Let’s look at the implementation.

/**
 * Releases in exclusive mode.  Implemented by unblocking one or
 * more threads if {@link #tryRelease} returns true.
 * This method can be used to implement method {@link Lock#unlock}.
 *
 * @param arg the release argument.  This value is conveyed to
 *        {@link #tryRelease} but is otherwise uninterpreted and
 *        can represent anything you like.
 * @return the value returned from {@link #tryRelease}
 */
public final boolean release(int arg) {
    / / (1)
    if (tryRelease(arg)) {
        Node h = head;
        / / (2)
        if(h ! =null&& h.waitStatus ! =0)
            (3)
            unparkSuccessor(h);
        return true;
    }
    return false;
}
Copy the code

Here it calls the release() method in AQS. We know from the comments that the tryRelease(ARG) method requires a concrete implementation class. Unlike tryAcquire, which is implemented by Sync’s subclasses FairSync and NonFairSync, tryRelease is implemented directly by Sync, so the unlocking method is the same for both FairSync and NonFairSync.

The tryRelease() method is called at (1).

protected final boolean tryRelease(int releases) {
    / / (1.1)
    int c = getState() - releases;
    / / (1.2)
    if(Thread.currentThread() ! = getExclusiveOwnerThread())throw new IllegalMonitorStateException();
    boolean free = false;
    / / (1.3)
    if (c == 0) {
        free = true;
        / / (1.4)
        setExclusiveOwnerThread(null);
    }
    / / (1.5)
    setState(c);
    / / (1.6)
    return free;
}
Copy the code
  • (1.1) gets the current state and assigns the value of state-Releases to C, which releases it with a value of 1, subtracting the number of reentries once.
  • CurrentThread () is the Thread that currently holds the lock. This makes sense because only the thread that holds the lock can perform the unlock operation.
  • (1.3) Here, it is determined whether C is equal to 0, that is, whether the unlocking operation can be performed at present. If it is unlocked, then (1.4) set the thread currently holding the lock as empty.
  • (1.5) Finally save the state state
  • (1.6) Return true only if the state is unlocked, and false if the state is simply updated to reduce reentrant times.

If tryRelease() returns false,n then the release method returns false and the unlock process is complete. This situation has not been released.

If tryRelease() returns true, with state equal to 0, the current thread releases the lock and executes the code in if. This part of the code can also be guessed to wake up the next thread in the current queue. (During the lock process, we know that whether the lock is fair or unfair, the thread that does not acquire the lock will be queued.)

Code (2) h! = null Determines whether the header is empty. If the header is empty, there is no thread waiting for the lock in the queue, so there is no need to wake up the thread. Similarly h.w. aitStatus! Lambda equals 0 does the same thing. The initial state of waitStatus is 0. If a thread is queued after the current node, the state of the previous node on the queued thread node is set to -1. For example, if T1 obtains the lock and a T2 queue is entered, the state of T1 will be set to -1. If T3 is entered, the state of T2 will be set to -1 (T1,T2 starts at 0). So if H.waitStatus! = -1, which means there is a queue behind the current node. In both cases, the queued thread is woken up.

/**
 * Wakes up node's successor, if one exists.
 *
 * @param node the node
 */
private void unparkSuccessor(Node node) {
    /* * If status is negative (i.e., possibly needing signal) try * to clear in anticipation of signalling. It is OK if this * fails or if status is changed by waiting thread. */
    int ws = node.waitStatus;
    / / (3.1)
    if (ws < 0)
        compareAndSetWaitStatus(node, ws, 0);

    /* * Thread to unpark is held in successor, which is normally * just the next node. But if cancelled or apparently null, * traverse backwards from tail to find the actual * non-cancelled successor. */
    Node s = node.next;
    / / (3.2)
    if (s == null || s.waitStatus > 0) {
        s = null;
        / / (3.3)
        for(Node t = tail; t ! =null&& t ! = node; t = t.prev)if (t.waitStatus <= 0)
                s = t;
    }
    if(s ! =null)
        LockSupport.unpark(s.thread);
}
Copy the code

Code (3) is the thread wake-up operation, passing in the node of the header and setting it to 0 if the waitStatus of (3.1) header is less than 0. (3.2) ws > 0 should be CANCELLED unless ws = 1 is CANCELLED. This node is cancelled due to timeout or interrupt.Nodes never leave this state. In particular a thread with cancelled Node never again blocks). Or the next node is empty. Normally s== null does not hold, we just need to wake up the next node. (3.3) However, if the node is cancelled or empty, we need to traverse from the end of the queue to the beginning of the queue to find the most advanced node.