This is the 24th day of my participation in the November Gwen Challenge. Check out the event details: The last Gwen Challenge 2021

Already an overview

A ReentrantLock is a reentrant exclusive lock, and only one thread can acquire the lock. Other threads that acquire the lock are blocked and placed in the LOCK’s AQS blocking queue. Let’s take a look at the Class diagram for ReentrantLock. ReentrantLock is also implemented according to AQS, and determines whether it is a fair lock or an unfair lock according to the input parameter of the ReentrantLock. The default is unfair lock. The Sync class directly inherits from AQS, and its subclasses NonfairSync and FairSync implement an unfair and fair lock acquisition strategy, respectively.

The state value at AQS indicates how many times a thread can reentrant the lock. By default, a value of state of 0 indicates that the current lock is not held by any thread. When a thread acquires the lock for the first time, it attempts to use CAS to set state to 1. If CAS succeeds, the current thread acquires the lock, and records that the lock is owned by the current thread. In the case that the thread does not release the lock, the state is changed to 2 the second time the lock is acquired, which is the number of reentrant times. When the thread releases the lock, it tries to use CAS to decrease the state value by 1, and if the state is 0 by 1, the lock is released.

ReentrantLock important methods in detail

Void the lock () method

public void lock(a) {
    sync.lock();
}
Copy the code

As shown in the code above, the lock() function of ReentrantLock is delegated to the Sync class, and the ReentrantLock constructor is used to select whether the Sync implementation is NonfailSync or FairSync. Let’s first look at the implementation of the Sync subclass NonfairSync.

static final class NonfairSync extends Sync {
    private static final long serialVersionUID = 7316153563782823691L;

    final void lock(a) {
    //CAS sets the status value
        if (compareAndSetState(0.1))
            setExclusiveOwnerThread(Thread.currentThread());
        else
            acquire(1); // call the AQS acqiure method
    }

    protected final boolean tryAcquire(int acquires) {
        returnnonfairTryAcquire(acquires); }}Copy the code

In NonfairSync code, because the default state value is 0, the first thread that calls the Lock sets the state value to 1 via CAS. If the CAS succeeds, the current thread has acquired the Lock. SetExclusiveOwnerThread sets the Lock holder to the current thread.

If another thread attempts to acquire the lock by calling the lock method, CAS fails and AQS acquire method is called. (Acquire passed a value of 1)

public final void acquire(int arg) {
Call the tryAcquire method overridden by ReentrantLock
//tryAcquire(arg) if false will put the current thread into the AQS blocking queue
    if(! tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) selfInterrupt(); }Copy the code

As mentioned in our previous article, AQS does not provide a tryAcquire method, so let’s first look at the implementation of an unfair lock.

final boolean nonfairTryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    // Get the state of the current lock
    int c = getState();
    // If the current AQS status is 0
    if (c == 0) {
        if (compareAndSetState(0, acquires)) {
            setExclusiveOwnerThread(current);
            return true; }}// If the current thread is the lock holder
    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;
}
Copy the code

The state of the lock is checked to see if it is 0, if so CAS grabs the lock, and returns true. If state is not 0, the lock is already held by a thread. If so, state+1(nexTC <0, reentrant count overflowed). If not, false.

Then let’s look at how FairSync overwrites the tryAcquire method to achieve fairness.

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;
}
Copy the code

What follows is important

The tryAcquire strategy for a fair lock is similar to that for an unfair lock, except that the HasqueueEstablishes method is added before the CAS is set.

public final boolean hasQueuedPredecessors(a) {

    Node t = tail; // Read fields in reverse initialization order
    Node h = head;
    Node s;
    returnh ! = t && ((s = h.next) ==null|| s.thread ! = Thread.currentThread()); }Copy the code

In the above code, when the return value is false, it means that the current node is not queued. There are two cases.

  1. (h! If =t) is false, return false directly. If both the first and last nodes are null, the queue is empty. So there’s no line; When the first and last nodes are not null and equal, there is only one node in the queue. When there is only one node in the queue, the second node is not queued according to the acquireQueued() function in AQS.

  2. When the (h! =t) returns true, indicating that at least two different nodes exist in the queue; (s = h.ext) == null Returns false to indicate that the first node has a successor node; s.thread ! CurrentThread () returns false to indicate that the Thread on the second node is the currentThread, so no queuing is required.

A return value of true means that the current node needs to be queued

When the (h! If ((s=h.next)==null) is false, select (s=h! = thread.currentthread ()) is true. If true, this means that the head node has a successor node and the successor node is not the currentThread, so the currentThread needs to queue.

Void lockInterruptibly () method

This method is similar to the Lock () method except that it responds to interrupts. The current thread throws InterruptedException if another thread calls the current thread’s interrupt() method while calling this method.

public void lockInterruptibly(a) throws InterruptedException {
    sync.acquireInterruptibly(1);
}
Copy the code
public final void acquireInterruptibly(int arg)
        throws InterruptedException {
        // If the current thread is interrupted, an exception is thrown
    if (Thread.interrupted())
        throw new InterruptedException();
        // Try to obtain the resource
    if(! tryAcquire(arg))// Call AQS that can be interrupted
        doAcquireInterruptibly(arg);
}
Copy the code

Boolean tryLock () method

Represents an attempt to acquire the lock. The current thread acquires the lock and returns true if it is not currently held by another thread, false otherwise. This method does not cause the current thread to block.

public boolean tryLock(a) {
    return sync.nonfairTryAcquire(1);
}
Copy the code
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;
}
Copy the code

Void unlock () method

If the current thread holds the lock, then calling this method will make the thread subtract 1 from the state value in the AQS held by the thread. If state=0 after subtracting 1, the current thread will release the lock; otherwise, the count is just state-1. If the thread does not hold the lock, sell IllegalMonitorStateException anomalies.

public void unlock(a) {
    sync.release(1);
}
Copy the code
public final boolean release(int arg) {
    if (tryRelease(arg)) {
        Node h = head;
        if(h ! =null&& h.waitStatus ! =0)
            unparkSuccessor(h);
        return true;
    }
    return false;
}
Copy the code
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;
}
Copy the code