ReentrantLock source code analysis

A RenntrantLock is an exclusive lock, that is, only one thread can acquire the lock. If one thread acquires the lock, other threads must wait.

1. Start with the unfair locks

Then explain the whole process of the lock, lock the flow chart is as follows:

ReentrantLock (ReentrantLock, ReentrantLock, ReentrantLock, ReentrantLock, ReentrantLock)

Which already realized the Lock and the Serializable interface, of which the Sync, NonfairSync and FairSync is its values, including the Sync inherited AbstractQueuedSynchronizer (AQS) class, NonfairSync and fairSync both inherit Sync class. Due to the characteristics of the Java language itself, that is, indirectly inherit AQS class. There are more methods in this class, which are overwritten by subclasses. To realize the process of adding and unlocking non-fairsync and fairSync.

Locking is actually quite simple, as shown below:

ReentrantLock lock = new ReentrantLock(); // No argument is passed in. The default is an unfair lock lock.lock();Copy the code

But how does locking actually happen? Listen to me carefully:

As shown in the figure below, the whole function call process completes the whole process of locking :(allow me to remove the previous picture, so as to prevent looking back, how inconvenient!!)

Ok, now enter the source code analysis:

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

Clicking on the lock() method, we can see an abstract method (inside the Sync class) as follows:

abstract void lock();
Copy the code

This method is overridden within Sync by its subclasses NonfairLock and fairLock; So if we go inside the NonfairSync class, we can see the following code:

Final void lock() {// First execute CAS to check whether the state is set successfully. If (compareAndSetState(0, 1)) setExclusiveOwnerThread(thread.currentThread ()); Acquire (1); else acquire(1); }Copy the code
// 1. The thread will first call tryAcquire() to try to acquire the lock; // 2. Suspend the thread that has not acquired the lock and wait to wake up; public final void acquire(int arg) { if (! tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) selfInterrupt(); }Copy the code

TryAcquire (ARG) acquireQueued(addWaiter(node.exclusive), arG)

Then enter the tryAcquire(ARG) source code analysis, find a click, as shown below, call a method, not afraid, continue to look, always can find the content you want to see:

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

NonfairTryAcquire (acquires) source code:

Final Boolean nonfairTryAcquire(int acquires) {final Thread current = thread.currentThread (); int c = getState(); If (compareAndSetState(0, acquires)) {if (compareAndSetState(0, acquires)) { The current thread acquires the synchronization status (lock) setExclusiveOwnerThread(current); // Set the current thread to exclusive return true; }} // Check whether the current thread is currently executing, if so, execute // here you can understand why ReentrantLock is ReentrantLock. 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; AcquireQueued (addWaiter(Node.exclusive), arg) ¶ If none of the above conditions is true, return false and acquireQueued(addWaiter(Node.exclusive), arg) will be executed. }Copy the code

AcquireQueued (addWaiter(node.exclusive), arg) acquireQueued(addWaiter(node.exclusive), arg)

Private Node addWaiter(Node mode) {// Encapsulate the current thread as a Node, Node = new Node(thread.currentThread (), mode); Pred = tail; if (pred ! = null) {// If the end node is not empty node.prev = pred; If (compareAndSetTail(pred, node)) {// Use CAS to insert the current into the end pred. Next = node; return node; } } enq(node); // If the second condition is not true, return node; The acquireQueued() method returns the current nodeCopy the code
Private Node enq(final Node Node) {// Loop for (;;) { Node t = tail; If (t == null) {if (compareAndSetHead(new Node()))) Tail = head; } else {// There is a tail node node.prev = t; If (compareAndSetTail(t, node)) {// Use CAS to add the passed node to the end. return t; // Returns the precursor node of the current node}}}}Copy the code

The key to the thread’s suspension, and the code that attempts to acquire the lock when it is awakened, is in the acquireQueued() method

final boolean acquireQueued(final Node node, int arg) { boolean failed = true; try { boolean interrupted = false; for (;;) {// Final Node p = node.predecessor(); If (p == head && tryAcquire(arg)) {setHead(node); // set the current node as the head node. // help GC failed = false; return interrupted; } // If the thread fails to acquire the lock, the thread should be suspended. Of course is hung thread parkAndCheckInterrupt () method performs the if (shouldParkAfterFailedAcquire (p, node) && parkAndCheckInterrupt()) interrupted = true; } } finally { if (failed) cancelAcquire(node); }}Copy the code

Then look at shouldParkAfterFailedAcquire (p, node) method and parkAndCheckInterrupt () method is how to hung thread.

Private static Boolean shouldParkAfterFailedAcquire (Node Mr Pred, Node Node) {/ / get the state of the precursor of the current Node Node int ws = Mr Pred. WaitStatus; If (ws == node. SIGNAL) // If the current Node is to be waked up, return true; CANCELED do {node.prev = pred = pred.prev; if (ws > 0) {CANCELED do {node.prev = pred.prev; } while (pred.waitStatus > 0); // Look for a state that is less than 0 (SIGNAL or CONDITION or PROPAGATE (currently not considered in RentrantLock)). } else { compareAndSetWaitStatus(pred, ws, Node.SIGNAL); } return false; }Copy the code

The parkAndCheckInterrupt() method looks like this:

private final boolean parkAndCheckInterrupt() { LockSupport.park(this); // Suspend the current Thread return thread.interrupted (); }Copy the code

At this point, the whole locking process is over, which may confuse some people. How is the synchronization queue constructed? Once unlocked, how does the thread acquire the lock? These will be analyzed in the next article.