ReentrantLock source code interpretation

Previously on Fair lock and unfair lock before reading; Reentrant lock, non-reentrant lock; Exclusive lock, shared lock. Have a certain understanding. The concept and introduction of various locks

1. ReentrantLock class structure

Its structure is very simple, as shown here

graph BT
ReentrantLock --implements--> Lock
ReentrantLock --implements--> Serializable

The ReentrantLock implementation of the lock can be divided into two categories, namely fair and unfair locks, by the ReentrantLock class of the two inner classes FairSync and NonfairSync. FiarSync NonfairSync and inherited the Sync, and Sync class inherits AbstractQueuedSynchronizer (AQS) class, so already is ultimately rely on AQS to realize locking.

Fair lock and a fair lock inheritance, because they are the top of the parent class are AbstractOwnableSynchronizer, so they are based on AQS exclusive locks, lock concept, please check

Graph BT FairSync inner classes, inherit to the -- -- > Sync - - - > AbstractQueuedSynchronizer - inheritance - > AbstractOwnableSynchronizer NonfairSync inner classes -- Inherit --> Sync

ReentrantLock fair lock source code interpretation

2.1 Constructor source code

The ReentrantLock is constructed as follows

/** * The constructor with no arguments builds an unfair lock */ by default
public ReentrantLock(a) {
        sync = new NonfairSync();
    }
/** * takes arguments to build a fair or unfair lock based on the arguments passed in */
public ReentrantLock(boolean fair) {
       sync = fair ? new FairSync() : new NonfairSync();
    }
​
​
Copy the code
  • When lock.lock() is called, the fairsync.lock () method is called, and the fairsync.lock () method calls acquire() in AQS. In the acquire() method of AQS, the tryAcquire() method of the subclass will be called first. At this time, since we create a fair lock, the tryAcquire() method of the FairSync class will be called

2.2 Fair lock creation and locking

// The lock is now exclusive, fair and reentrant
ReentrantLock lock = new ReentrantLock(true);
    try{
        lock.lock(); // Check to see if the lock can be obtained by calling tryAcquire, the subclass's implementation
        System.out.println("Hello World");
    }finally {
        lock.unlock(); / / unlock
    }
Copy the code
  • A fair lock is created, so FailSync’s lock() method is called.
static final class FairSync extends Sync {
​
    final void lock(a) {
        // Call AQS acquire()
        acquire(1); }}Copy the code

2.3 Source code interpretation of acquire() method in AQS

  • The AQS template method is called in FailSync’s lock() method:acquire()The source and interpretation of the method are below. The final locking logic is implemented in the acquire() method.acquire()The source code is as follows:

The -tryacquire () method attempts to obtain the synchronization state. If the method returns true, the synchronization state has been obtained successfully. If tryAcquire() returns true, the synchronization status cannot be obtained. In this case, the thread successfully acquired the lock, and the acquire() method terminates and returns. Case 2: When tryAcquire() returns false, the thread has not acquired the lock and needs to be added to the synchronization queue. Execute the addWaiter(Node.EXCLUSIVE) method (Node.EXCLUSIVE is an EXCLUSIVE lock; Node.shared is a SHARED lock), which puts the current thread into an execution queue. In an exclusive lock, the addWaiter() argument is null and the addWaiter() method returns the node of the current thread. The acquireQueued() method is then called, in which the node represented by the current thread is determined to be the second node (why is it the second node? If the thread does not acquire the lock, the thread will block. If the thread does not acquire the lock, the thread will block. If a lock is acquired, it is returned. The acquireQueued() method returns true, indicating that the thread has woken up after being interrupted. In this case, the if condition is successful and the selfInterrupt() method is executed, which sets the interrupt flag bit of the current thread to the interrupted state. If the acquireQueued() method returns false, the thread is not woken up after being interrupted, and the if condition will not succeed. Acquire () method execution ends

Summary: The acquire() method terminates only when the thread acquires the lock; If the thread does not acquire the lock, it will always block in the acquireQueued() method, and the acquire() method will never end.

  public final void acquire(int arg) {
        if(! tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg))// Call the selfInterrupt() method to reset the interrupt identifier
            selfInterrupt();
  }
Copy the code

2.3.1 FairSync(fair lock) tryAcquire method interpretation

TryAcquire returns ture to indicate the acquired lock. A working queue is maintained in AQS. At the same time we find a setExclusiveOwnerThread() method, which sets the exclusivity of lock changes.

 protected final boolean tryAcquire(int acquires) {
     // Get the current thread
            final Thread current = Thread.currentThread();
     // Get the lock status
            int c = getState();
     // c==0 means no one else has the lock
            if (c == 0) {
                // Since FairSync is a fair lock, we cannot jump the queue. We need to check whether there are other threads in the queue
                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; }}/ / -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- gorgeous line -- -- -- -- -- -- -- -- -- -- -- -- -- -- -
/ / internal class FairSync inherit internal Sync, inner class Sync AbstractQueuedSynchronizer inheritance
  public final boolean hasQueuedPredecessors(a) {
        Node t = tail; 
        Node h = head;
        Node s;
    // If false is returned, no threads are queued
        returnh ! = t &&((s = h.next) ==null|| s.thread ! = Thread.currentThread()); }Copy the code

2.3.2 addWaiter(Node.exclusive) interpretation of FairSync

Returns the current node to execute.

private Node addWaiter(Node mode) {
        Node node = new Node(Thread.currentThread(), mode);
        Node pred = tail;
  // Insert the last node of the list using CAS
        if(pred ! =null) {
            node.prev = pred;
            if (compareAndSetTail(pred, node)) {
                pred.next = node;
                returnnode; }}// A queue used to initialize the thread of execution
        enq(node);
        return node;
    }
/ / -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- gorgeous line -- -- -- -- -- -- -- -- -- -- -- -- -- -- -
 private Node enq(final Node node) {
        for (;;) {
          // The last node =null initializes one and makes tail=head
            Node t = tail;
            if (t == null) { // Must initialize
                if (compareAndSetHead(new Node()))
                    tail = head;
            } else {
              // Finally, connect the current thread of execution to the end
                node.prev = t;
                if (compareAndSetTail(t, node)) {
                    t.next = node;
                    returnt; }}}}Copy the code

2.3.3 FairSync(Fair lock) acquireQueued() source interpretation

  • After the addWaiter() method is executed, the acquireQueued() method is executed. The purpose of this method is to allow the thread to acquire the lock in a continuous manner. If it does not acquire the lock, it will block in the method until it has acquired the lock, and then it will return from the method.
  • Anchor introduces three methods of threading here. Interrupt sends instructions to interrupt a thread, depending on the situation. IsInterrupted Checks the interrupted status, but does not clear interruption information (only if interrupted). Interrupted checks whether the thread is interrupted and resets its interrupted status to uninterrupted after returning.
// True if interrupted while waiting
final boolean acquireQueued(final Node node, int arg) {
        boolean failed = true;
        try {
            boolean interrupted = false;
          // Block to get lock
            for (;;) {
                final Node p = node.predecessor();
              // The previous node was the first node in the queue to attempt to acquire the lock
                if (p == head && tryAcquire(arg)) {
                  / / set the head
                    setHead(node);
                  // Other queues are cleared
                    p.next = null; // help GC
                  // Prevent deadlock caused by errors during operation
                    failed = false;
                    return interrupted;
                }
              // waitStatus >0 is a stopped aborted thread that does not need to be queued
              // Only waitStatus = -1 (SIGNAL) will wake up,
              // parkAndCheckInterrupt stops the current thread and detects the interrupted state, clearing the interrupted state and waiting to wake up the thread while the lock is released
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true; }}finally {
            if(failed) cancelAcquire(node); }}Copy the code

Unlock () unlock source code interpretation

The process of releasing locks

ReentrantLock’s UNLOCK method calls AQS’s Release () method

2. In release, the lock will be tried to be released first (because it is a flushable lock, it needs to be tried to be released, but not necessarily released).

3. If the thread fails to be released, it will return. If the thread succeeds, it will wake up the second thread in the queue to continue executing

  public final boolean release(int arg) {
    // Release the lock by calling tryRelease of Sync, the inner class in ReentrantLock
        if (tryRelease(arg)) {
            Node h = head;
            // Wake up the queue thread waitStatus! If = 0, the queue exists, and the thread in the queue is woken up
            if(h ! =null&& h.waitStatus ! =0)
                unparkSuccessor(h);
            return true;
        }
        return false;
    }
Copy the code

3.1 Interpretation of tryRelease source code

ReentrantLock is a ReentrantLock and an exclusive lock, so it is necessary to release the thread information held by the exclusive lock.

// A return of true indicates that the lock has been released. A non-0 value indicates that other methods of the current thread still hold the lock
protected final boolean tryRelease(int releases) {
            int c = getState() - releases;
                        // Not the current thread, failed to release the lock
            if(Thread.currentThread() ! = getExclusiveOwnerThread())throw new IllegalMonitorStateException();
            boolean free = false;
                    // c==0 Indicates that the lock is completely released and the exclusive lock is released
            if (c == 0) {
                free = true;
                setExclusiveOwnerThread(null);
            }
            setState(c);
            return free;
        }
Copy the code

3.2 Unparkantecedents source code interpretation

Click to jump to the awakened thread execution location

private void unparkSuccessor(Node node) {
        int ws = node.waitStatus;
            // Clear the waitStatus information
        if (ws < 0)
            compareAndSetWaitStatus(node, ws, 0);
        Node s = node.next;
        // The waitStatus of the next node in the queue indicates that the thread does not need to be woken up,
            // Discard all threads that do not need to be awakened
        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;
        }
            // Wake up the thread
        if(s ! =null)
            LockSupport.unpark(s.thread);
    }
Copy the code

3, summary

ReentrantLock is a pessimistic lock, a ReentrantLock, an exclusive lock, and an implementation that can be specified as fair or unfair by constructing parameters. It is more malleable than synchronized. And it can control whether the current thread continues to wait for the lock through the status of waitStatus. ReentrantLock fair lock source code has been all of us pulled out, please look at the official look and combined with their own understanding of debug debug a deeper understanding.

Let’s take a look at ReentrantLock’s unfair lock source code, which is almost the same as the fair lock source code.

ReentrantLock unfair lock source code interpretation

4.1 Unfair lock creation and lock

The inner class object created with the argument false is NonfairSync.

// The lock is now an exclusive lock, not a fair lock, reentrant lock
ReentrantLock lock = new ReentrantLock(false);
    try{
        lock.lock(); // Check to see if the lock can be obtained by calling tryAcquire, the subclass's implementation
        System.out.println("Hello World");
    }finally {
        lock.unlock(); / / unlock
    }
Copy the code

A fair lock calls NonfairSync’s lock method. Check the source code to see if an unfair lock can be obtained by CAS.

If the lock is acquired, the thread is added to its own exclusive lock to save the current thread.

If the lock cannot be acquired, acquire(1) is called as if it were a fair lock and the lock is added to the thread queue. The tryAcquire method of NonfairSync is called to acquire the lock, and then Sync is called to attempt to acquire the lock.

final void lock(a) {
     if (compareAndSetState(0.1))
        setExclusiveOwnerThread(Thread.currentThread());
     else
        acquire(1);
}
protected final boolean tryAcquire(int acquires) {
            return nonfairTryAcquire(acquires);
}
Copy the code

4.2 Unfair lock acquisition lock source interpretation

Through the source code below, we can see that the source code of the unfair lock and the source code of the fair lock is only a difference! The hasqueuedToraise () method, which determines whether the current thread is the first in a queue and can be used to cut in.

 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

The difference between Synchronized and ReentrantLock

synchronized ReentrantLock
Lock implementation mechanism Object header monitor mode Rely on AQS
flexibility inflexible Support interrupt response, timeout, supermarket access lock
Release lock form Automatic release Display call unlock()
Lock types supported Not fair lock Fair locks & Unfair locks
Conditions of the queue Single conditional queue Multiple conditional queue
Whether it is reentrant support support

Due to the limited level of the author, you are welcome to feedback and correct the article is not correct, thanks to 🙏

Due to the limited level of the author, you are welcome to feedback and correct the article is not correct, thanks to 🙏

Due to the limited level of the author, you are welcome to feedback and correct the article is not correct, thanks to 🙏

Refer to the article

Meaning of waitStatus in AQS

Thread the interrupt

Article 3

Article 4

Meituan’s AQS original

A thousand words AQS