Send you the following Java learning materials, at the end of the article there is a way to receive







preface

ReentrantLock implements thread synchronization just like synchronized, but it is more flexible and powerful than synchronized. It adds advanced functions such as polling, timeout, interrupt and so on, and can control thread synchronization more finely. ReentrantLock is a lock based on AQS implementation, which supports both fair lock and non-fair lock. It is also a reentrant lock and a spin lock.

In this chapter, we’ll explore the locking mechanism of ReentrantLock based on the source code. If you don’t understand it properly, please feel free to share your comments.

Flowchart of locking mechanism of fair lock and unfair lock:

A fair lock for ReentrantLock

Using ReentrantLock’s fair lock, call lock to lock. The source code for the lock method is as follows:

`final void lock() {` `acquire(1); ` `}` `public final void acquire(int arg) {` `if (! tryAcquire(arg) &&` `acquireQueued(addWaiter(Node.EXCLUSIVE), arg))` `selfInterrupt(); ` ` `}

You can see that FairLock calls tryAcquire first. The tryAcquire source code is as follows:

`/**` `* Fair version of tryAcquire. Don't grant access unless` `* recursive call or no waiters or is first.` `*/` `protected final boolean tryAcquire(int acquires) {` `final Thread current = Thread.currentThread(); ` `int c = getState(); ' 'if (c == 0) {'' '// If there is no waiting thread in the queue or the current thread is at the head of the queue, lock' ' 'based on CAS hasQueuedPredecessors() &&` `compareAndSetState(0, acquires)) {` `setExclusiveOwnerThread(current); ` `return true; ' ' '} ' ' '// Do you want to buy new Acquires?' 'else if (current == GetExclusiveOwnerThread ()) {'' int nextC = C + Acquires; ` `if (nextc < 0)` `throw new Error("Maximum lock count exceeded"); ` `setState(nextc); ` `return true; ` `}` `return false; ` ` `}

As can be seen from the source code, when state is 0, that is, when no thread acquires the lock, FairLock first calls the HasQueuedNational () method to check whether there are waiting threads in the queue or whether it is on the head of the queue. If there is no waiting thread in the queue or if you are on the head of the queue then call compareAndSetSetState () to lock based on the CAS operation. If CAS succeeds then call SetExclusiveOwnerThread to set the locked thread to the current thread.

ReentrantLock is a ReentrantLock. If the state value is not 0, the thread holding the lock is determined to be the current thread. If the state value is not 0, the thread holding the lock is determined to be the current thread.

If we cannot obtain the lock by calling tryAcquire, FairLock will call the addWaiter() method to join the current thread to the CLH queue. The addWaiter method has the following source:

`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; ` `if (pred ! = null) {` `node.prev = pred; ' 'if (compareAndSettail (pred, node)) {'' pred.next = node; ` `return node; // If the CAS operation fails, enq spin-queue ' 'enq(node) is called; ` `return node; ` `}` `private Node enq(final Node node) {` `for (;;) {` `Node t = tail; ` `if (t == null) { // Must initialize` `if (compareAndSetHead(new Node()))` `tail = head; ` `} else {` `node.prev = t; ` `if (compareAndSetTail(t, node)) {` `t.next = node; ` `return t; ' '} ' ' '} ' ' '} '

In the addWaiter method, the CAS operation puts the current thread node to the tail of the queue. If the first CAS fails, the enq method will be called and the current thread will be queued several times with the spin-based CAS operation.

After the current thread has been queued, AcquireRequeued will call the AcquireRequeued method to implement the spin lock. The source code for AcquireRequeued is as follows:

`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); ` `p.next = null; // help GC` `failed = false; ` `return interrupted; ` `}` `if (shouldParkAfterFailedAcquire(p, node) &&` `parkAndCheckInterrupt())` `interrupted = true; ` `}` `} finally {` `if (failed)` `cancelAcquire(node); ` ` `} ` `}

In the AcquireRequeED method, each spin will first call the predecessor() method to get the previous node of the current thread node. If the predecessor node is found to be the head node, then the current thread node is in opposition (the head is a puppet node), then tryAcquire will be called for dedicated locking.

If the current thread is not in the queue, head will call shouldParkAfterFailedAcquire method can judge whether the current thread nodes hang know wake his former node releases the lock, if you can hang, call the parkAndCheckInterrupt suspend operation. In addition, pay attention to the official account of back-end architect, reply to “interview”, and send you a treasure book of interview questions!

ShouldParkAfterFailedAcquire source code is as follows:

`private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {` `int ws = pred.waitStatus; ` `if (ws == Node.SIGNAL)` `/*` `* This node has already set status asking a release` `* to signal it, so it can safely park.` `*/` `return true; ` `if (ws > 0) {` `/*` `* Predecessor was cancelled. Skip over predecessors and` `* indicate retry.` `*/` `do {` `node.prev = pred = pred.prev; ` `} while (pred.waitStatus > 0); ` `pred.next = node; ` `} else {` `/*` `* waitStatus must be 0 or PROPAGATE. Indicate that we` `* need a signal, but don't park yet. Caller will need to` `* retry to make sure it cannot acquire before parking.` `*/` `compareAndSetWaitStatus(pred, ws, Node.SIGNAL); ` `}` `return false; ` ` `}

ShouldParkAfterFailedAcquire source, if the current thread node node before waitStatus status to SIGNAL (1), showed that nodes have been set up after wake up when the lock is released (unpark) its nodes, The current thread node can then safely block (park) and wait for its predecessor to awaken itself at unlock to continue trying to lock.

If the previous node’s WaitStatus status is >0, known as Cancelled (1), which indicates that the previous node has abandoned the lock, it will continue until it finds a thread node that can awaken it at unlock. If the state of the previous node WaitStatus is CONDITION (-2), that is, it is in a waiting CONDITION, then the state of the previous node will be set to SIGNAL based on CAS (actively intervening the previous node to wake itself up).

ParkAndCheckInterrupt source code:

`private final boolean parkAndCheckInterrupt() {` `LockSupport.park(this); ` `return Thread.interrupted(); ` ` `}

Unfair locks for ReentrantLock

Unlike the fair lock mechanism, the CAS operation is used to try to lock the queue, regardless of whether there are threads in the queue. The source code is as follows:

`final void lock() {` `if (compareAndSetState(0, 1))` `setExclusiveOwnerThread(Thread.currentThread()); ` `else` `acquire(1); ` `}` `public final void acquire(int arg) {` `if (! tryAcquire(arg) &&` `acquireQueued(addWaiter(Node.EXCLUSIVE), arg))` `selfInterrupt(); ` ` `}

If the CAS operation fails (and the door is shut at first), then the acquire method is called for a subsequent attempt and wait. The tryAcquire method is called first to try again to acquire the lock or reentrant the lock. The noFairLockd tryAcquire method has the following source:

`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; ` ` `}

We can see that the only difference between noFairLock’s tryAcquire method and FairLock’s tryAcquire method is that noFairLock does not need to call HasQueued24 method to determine whether there are other threads in the queue before attempting to apply a lock. Instead, the CAS operation locks directly.

If the next attempt at locking or lock reentrancy fails, the exact same action as fair locking will be followed (without further detail) : AddWaiter — > Spin Locking (AcquireRequed).

3. Unlock

Having said the lock mechanism of fair lock and non-fair lock, let’s take a brief look at the unlock source code. The source of the unlock is as follows:

`public void unlock() {` `sync.release(1); ' '} ' 'public final Boolean release(int arg) {'' if (tryRelease(arg)) {' 'Node h = head; ' '// Wake up the blocked thread node after the lock is released. = null && h.waitStatus ! = 0)` `unparkSuccessor(h); ` `return true; ` `}` `return false; ` ` `}

This paper mainly explores the locking process of fair locks and non-fair locks, the differences and similarities between them in acquiring locks. The whole article involves the following points:

  1. Lock process of fair lock and non-fair lock
  2. The implementation of spin lock and blocking wake-up during spin
  3. Implementation of reentrant locks
  4. CLH queue

Note: If there is any incomprehension in this article, please give your valuable comments and don’t hesitate to comment.