JDK growth 20: AQS underlying principle of reenranstlock release lock

In the first two sections, you should master the core logic of ReentrantLock locking success and locking failure, and how to join the team through the three components in AQS. Today, let's look at:

In ReentrantLock, the logic when a thread releases a lock

Lock release process and source code analysis

Lock release process and source code analysis

At present, the results after thread 1 and thread 2 use ReentrantLock.lock() are as follows:

Thread 2 waits in the queue, thread 1 holds the lock, state=1, owner is thread 1, the element in the queue is thread 2, the queue is a two-way linked list composed of Node nodes, and the head Node is empty. The details of the queue are as follows:

At this time, suppose thread 1 calls the unlock() method to release the lock. What will it do?

First, let's look at the released Code:

  public void unlock() {
    sync.release(1);
  }

Quite simply, the Sync component (AQS) is used to release the lock. release() method

public final boolean release(int arg) {
    if (tryRelease(arg)) {
      Node h = head;
      if (h != null && h.waitStatus != 0)
        unparkSuccessor(h);
      return true;
    }
    return false;
  }

The core is divided into two steps:

1) tryRelease method to release the state and owner variables

2) Unparksuccess method, wake up queue element

Let's take a look at the method of releasing the variable tryRelease first:

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

The logic of this method is very clear. The current thread is thread 1, state=1, and the incoming parameter releases is 1, indicating that the lock is released once. Obviously, if the state decreases by 1 and becomes 0, the owner will also be set to null, indicating that no thread holds the lock. Then set the state to 0 to end.

The whole process is shown in the figure below:

After modifying the values of the owner and state components, the second step is to wake up the thread waiting in the queue.

public final boolean release(int arg) {
  if (tryRelease(arg)) {
    Node h = head;
    if (h != null && h.waitStatus != 0)
      unparkSuccessor(h);
    return true;
  }
  return false;
}

First, after successfully releasing the lock, use the h pointer to point to the head of the current queue to judge whether there are waiting elements in the queue. Note that the header element waitStatus cannot be 0. If it is 0, it means that the queue has only one empty node and there are no waiting elements in the queue. Because the waitStatus of the header node will be changed to - 1, SIGNAL after the queue element.

Then you enter the unpartsuccess method. From the name, it is to restore the thread suspended after the h node. (PS: there are always some words with similar semantics in the JDK source code, such as first last, head tail, success - predecessor and prev next. In fact, they all mean before and after and are equivalent. We should be clear about this.)

private void unparkSuccessor(Node node) {
  int ws = node.waitStatus;
  if (ws < 0)
    compareAndSetWaitStatus(node, ws, 0);

  Node s = node.next;
  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;
  }

  if (s != null)
    LockSupport.unpark(s.thread);
}

Node is the input parameter h, head node. First, change the head node waitStatus from - 1 to 0.

Next, s=node.next indicates the Node queued by thread 2. S is not empty, and LockSupport.unpark(s.thread) is executed; Thread 2 was awakened.

Finally, the whole method returns, and release returns true, indicating that the lock is released successfully. As shown in the figure below:

Thread 2 suspended before this time, remember? Thread 2 once performed the LockSupport.park(t). If the thread is suspended, the code will hang wait in that line.

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

Thread 2 will then execute. As long as thread 2 has not been interrupted, the return must be false, and the following if conditions will not hold.

 if (shouldParkAfterFailedAcquire(p, node) &&parkAndCheckInterrupt())

Then go back to the for loop and repeat the tryAcquire operation. Thread 2 attempts to obtain the lock again. At this time, it is assumed that no other thread comes to add the lock. Thread 2 will naturally acquire the lock and set state=1 and owner = thread 2.

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

The whole lock release process above can be summarized as follows:

Thinking & long picture summary ReentrantLock

Thinking & long picture summary ReentrantLock

We analyze the locking and releasing of ReentrantLock. The last summary of today is actually: from context to detail, and then from detail context.

In addition, in order to let everyone better master the AQS principle of ReentrantLock. Here is a summary for you.,

First of all, the principle can be simply stated in one sentence: suppose that two threads acquire locks at the same time, only one can obtain locks successfully, and the other will enter a queue to wait. The bottom layer is achieved by combining the three components of state, owner, Node queue and AQS.

The lock and release process of ReentrantLock core can be summarized as follows:

This article is composed of blog one article multi posting platform OpenWrite release!

Tags: Java Big Data Back-end

Posted on Fri, 29 Oct 2021 09:29:02 -0400 by gijs25