Hello World, I’m Tian, and today I’m going to share with you the ReentrantLock of concurrent programming.

Starting out in Java development, or prior to JDK1.5, synchronized locks were the first thing that came to mind when you encountered concurrency problems.

We talked about the use of synchronous locks in a previous article.

Among them, there are the following problems when using synchronized:

  • Synchronized is an uninterruptible lock that can be released only after the thread completes execution.

  • Synchronized is an unfair lock.

  • Before the optimization of Synchronized, the performance of Synchronized was not optimistic, but after the introduction of biased lock and lightweight lock (spin lock), the performance has been improved.

  • Synchronized is not fine-grained and flexible enough.

  • .

So Doug Lea created a ReentrantLock, which was created in JDK1.5 under the java.util.concurrent (JUC) package directory.

Efforts from today, success from “zero” start

In this paper, the content

It can be roughly divided into the following core knowledge points:

1. Simply use ReentrantLock

2. Fair and unfair locks

3. Source code analysis of state and thread wait queue (CLH) in AQS

4. Fair lock and unfair lock Methods and differences

5. How is timeout lock acquisition implemented

6. How is the lock released

The article is a bit long because it involves partial source code analysis.

Key knowledge points

State, thread synchronization queue,CASAnd death cycle

Simple to useReentrantLock

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class ReentrantLockDemo {
    private static final Lock lock = new ReentrantLock();

    public static void main(String[] args) {
        new Thread(new Runnable() {
            @Override
            public void run(a) { test(); }},Thread 1 "").start();
        new Thread(new Runnable() {
            @Override
            public void run(a) { test(); }},Thread 2 "").start();
    }

    public static void test(a) {
        try {
            / / acquiring a lock
            lock.lock();
            System.out.println(Thread.currentThread().getName() + "Lock has been obtained.");
            // Business code, using parts takes 100 ms
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            // Release the lock in finally.
            lock.unlock();
            System.out.println(Thread.currentThread().getName() + "Release the lock."); }}}Copy the code

The output

Thread 1 has acquired the lock thread 1 has released the lock thread 2 has acquired the lock thread 2 has released the lockCopy the code

The effect is the same as synchronized: Thread 1 acquires the lock, and thread 2 can acquire the lock only after thread 1 releases the lock.

Pay attention to

To prevent locks from not being released and resulting in deadlocks, it is highly recommended that lock releases be placed in the finally module.

ReentrantLockOverall introduction

A ReentrantLock is a ReentrantLock. Synchronized is also reentrant.

Enter Java. Util. Concurrent. The locks. Already, found that already have three inner classes;

The corresponding UML class diagram structure is as follows:

Saw a familiar figure in Java. The util. Concurrent. The locks. AbstractQueuedSynchronizer (jianghu AQS for short), this is the legendary AQS (synchronous queue).

We all know there are fair locks and unfair locks. In the class diagram above, NonfairSync is an unfair lock and FairSync is a fair lock.

Lock method and unlock method
private final Sync sync;
//ReentrantLock takes no arguments constructor
public ReentrantLock(a) {
     sync = new NonfairSync();
}
public void lock(a) {
     sync.lock();
}
Copy the code

You know from the no-argument constructor that ReentrantLock defaults to an unfair lock.

Go to lock in NonfairSync

// Lock, use final modifier, submethods cannot be overridden
final void lock(a) {
    // Use the CAS operation to change the state, indicating the lock scrambling operation
     if (compareAndSetState(0.1))
         // Set the thread that currently obtains the lock state
         setExclusiveOwnerThread(Thread.currentThread());
      else
          // Try to get the lock
         acquire(1);
}
Copy the code

The following three methods are all in AQS.

compareAndSetState();// Use the CAS operation to change the state, indicating the lock scrambling operation
setExclusiveOwnerThread();// Set the thread that currently obtains the lock state
acquire();// Try to get the lock
Copy the code

Not to mention the unlock method in ReentrantLock, which starts directly from AQS

/ / unlock
public void unlock(a) {
     sync.release(1);
}
Copy the code

Go to the release method in AQS

/ / release
public final boolean release(int arg) {
        if (tryRelease(arg)) {
            Node h = head;
            if(h ! =null&& h.waitStatus ! =0)
                unparkSuccessor(h);
            return true;
        }
        return false;
}
// Empty methods are left to subclasses to implement
protected boolean tryRelease(int arg) {
        throw new UnsupportedOperationException();
}
Copy the code

Can’t talk, or to put here, because of the fair lock and the fair lock are inherited from Java. The util. Concurrent. The locks. AbstractQueuedSynchronizer. So we have to talk about AQS first, first AQS clear up the above ReentrantLock before we can continue.

in-depthAQS

AbstractQueuedSynchronizer such as its name suggests, the abstract queue type synchronizer, AQS defines a set of synchronizer framework for multithreaded access to a Shared resource, many synchronization class implements are dependent on it, Such as commonly used already/Semaphore/CountDownLatch concurrent utility class, and blocking queue and the realization of the thread pool are used to AQS. Since it is an abstract class, you can imagine that there are likely to be abstract methods and already implemented methods. If you look at the source code, you will find that many methods are empty and need to be implemented by subclasses themselves.

It maintains a volatile int state (representing shared resources) and a FIFO thread wait queue (which is entered when multithreaded contention for resources is blocked).

state

Volatile is the key keyword here. The semantics of volatile are not covered here. There are three ways to access state:

There is a very important role in AQS

    private volatile int state;
    //get set
    // Use the CAS operation to change the state, indicating the lock scrambling operation
    protected final boolean compareAndSetState(int expect, int update) {
        return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
    }
Copy the code

This code is actually very simple logic, is by **CAS optimistic lock ** to do comparison and replacement. If the current value of state in memory is equal to the expected value expect, replace it with update and return true on success, false otherwise.

The Unsafe operation refers to the Unsafe class operation, and the first level refers to the meaning of the state property. The Unsafe class will be covered in an article later on.

You may have noticed that there are three methods involved in the lock and unlock methods, each of which has an argument of 1.

compareAndSetState(0, 1)

acquire(1);

release(1);

  • When state is 0, it indicates that there is no lock

  • When state>0, another thread has acquired the lock, i.e. state=1. However, because ReentrantLock allows ReentrantLock, the state increases when the same thread acquires the lock more than once, for example, ReentrantLock 5 times. When the lock is released, it also needs to be released 5 times until state=0 before any other thread is eligible to acquire the lock.

Note:

Different implementation classes of AQS have different meanings for the state field, which must be noted.

Thread wait queue

Moving on to queues in AQS, the implementation of this queue is a bidirectional linked list, called sync Queue, which represents a collection of all threads waiting for a lock, similar to the wait set described earlier in synchronized. Each Node Node stores the synchronization state, wait state, precursor and successor nodes of the current thread, etc.

As we mentioned earlier, using queues in concurrent programming usually involves wrapping the current thread into some type of data structure and throwing it into a waiting queue. Let’s first look at the structure of each node in the queue:

static final class Node {
        // Waiting flags in shared mode
        static final Node SHARED = new Node();
        // Waiting flags in exclusive mode
        static final Node EXCLUSIVE = null;
 
        // The thread's wait state indicates that the thread has been canceled
        static final int CANCELLED =  1;
        // The wait state of a thread indicates that a successor thread needs to be awakened
        static final int SIGNAL    = -1;
        // The wait state of a thread indicates that the thread is on a Condtion
        static final int CONDITION = -2;
        
        // the next acquireShared needs to be propagated unconditionally
        static final int PROPAGATE = -3;
 
        /** * SIGNAL: its successor must be CANCELLED ** if the synchronization status of the current node is released or CANCELLED while the current node's successor is waiting. CONDITION: The current node is in the wait queue, the node is transferred to the synchronization queue only when the node's state is set to 0. PROPAGATE: The next shared mode synchronization state fetch will unconditionally propagate the initial value of waitStatus at 0, using CAS to modify the node state */
        volatile int waitStatus;
 
        /** * The current thread relies on this node to check waitStatus, which is allocated when queued. * And is only cancelled on exit (for GC), the head node is never cancelled, a node becomes the head node * only as a result of successfully acquiring the lock, a cancelled thread never obtains the lock, the thread only cancels itself, * not involving other nodes */
        volatile Node prev;
 
        /** * The successor node of the current node, which is only invoked by the current thread, is allocated on queuing, adjusted when bypassing the cancelled precursor node *, and cancelled when exiting the queue (for GC) ** If a node's next is empty, we can double-check its prev from the tail * Next of the canceled node is set to point to the node itself rather than null, to make isOnSyncQueue easier to manipulate */
        volatile Node next;
 
        // Thread of the current node, used after initialization, invalid after use
        volatile Thread thread;
 
        /** * the wait condition linked to the next node, or the special value SHARED, because conditional queues can only be accessed in exclusive mode, * So we just need a simple connection queue to hold the nodes while we wait, and then transfer them to the queue to retrieve them * Since the condition can only be exclusive, we represent the shared mode by using special values */
        Node nextWaiter;
 
        // Returns true if the node is waiting in shared mode
        final boolean isShared(a) {
            return nextWaiter == SHARED;
        }
 
        // Return the precursor node of the current node, if null, throw a null pointer exception
        final Node predecessor(a) throws NullPointerException {
            Node p = prev;
            if (p == null)
                throw new NullPointerException();
            else
                return p;
        }
        // The tag used to set up the initialization head or SHARED
        Node() {    
        }
        // Specify thread and pattern constructors
        Node(Thread thread, Node mode) {     
            this.nextWaiter = mode;
            this.thread = thread;
        }
        // Specify thread and node state constructors
        Node(Thread thread, int waitStatus) { 
            this.waitStatus = waitStatus;
            this.thread = thread; }}Copy the code

Sync Queue (AQS) : Sync queue (AQS) : Sync Queue (AQS) : Sync queue

// The header, which does not represent any thread, is a dummy
private transient volatile Node head;
// Each thread that requests a lock is added to the end of the queue
private transient volatile Node tail;
Copy the code

Both are volatile to ensure that fields are visible across multiple threads.

The synchronization queue as a whole should look like this:

AQS is a CLH queue. Its head is always a dummy node, which does not represent any thread (in some cases, it represents the thread holding the lock). Therefore, the thread attribute of the Node to which head points is always null. Only all nodes from the secondary node represent all threads waiting for locks. In other words, if the current thread does not grab the lock and is thrown into the queue as a Node, even if the queue is empty, it will be the second in line, and we will create a dummy Node in front of it (more on this later). For the sake of description, the queue with the head node removed is called the wait queue, and the nodes in the queue represent all the threads waiting for the lock:

Before moving on, let’s summarize the meanings of each Node parameter in the figure above:

  • Thread: Indicates the thread represented by the current Node

  • WaitStatus: indicates the waiting state of the node. In shared lock mode, only three states need to be paid attention to: SIGNAL CANCELLED initial (0)

  • Prev Next: Precursors and successors of nodes

  • NextWaiter: indicates that the lock is in exclusive mode. The value is always null

There are three important points in AQS: state, wait queue and

The current thread holding the lock, note the property by inheritance from AbstractOwnableSynchronizer

private transient Thread exclusiveOwnerThread; // Set the current lock to be held by the thread lock, Protected final void setExclusiveOwnerThread(Thread Thread) {exclusiveOwnerThread = thread; }Copy the code

Having covered the whole of AQS, let’s go back to the lock method above. Our goal today is to fix ReentrantLock. Keep reading.

Deep lock method

Not fair lock

private final Sync sync; Public ReentrantLock() {sync = new NonfairSync(); } public void lock() { sync.lock(); }Copy the code

Lock method in NonfairSync

static final class NonfairSync extends Sync { private static final long serialVersionUID = 7316153563782823691L; // If (compareAndSetState(0, 1)) // if (compareAndSetState(0, 1)) SetExclusiveOwnerThread (thread.currentThread ()); Else // Method 3: Try to acquire lock acquire(1); } protected final Boolean tryAcquire(int acquires) {//nonfairTryAcquire (int acquires) ¶ nonfairTryAcquire(acquires); }}Copy the code

Methods 1 and 2 in Lock were covered earlier. So let’s talk about method 3.

Here method 3:acquire is a method in AQS

Public final void acquire(int arg) {if (! tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)){ selfInterrupt(); }}Copy the code

Acquire method there are four methods:

TryAcquire // Try to acquire the lock addWaiter// add a wait node acquireQueued// selfInterruptCopy the code

The above methods are explained one by one below

TryAcquire method

This method is an empty method in AQS, leaving subclasses to implement themselves. We used an unfair lock above. So back to NonfairSync

//acquires=1
protected final boolean tryAcquire(int acquires) {
    return nonfairTryAcquire(acquires);
}

Copy the code

Here the method nonfairTryAcquire is the method of Sync

//acquires=1 final boolean nonfairTryAcquire(int acquires) { final Thread current = Thread.currentThread(); int c = getState(); If (c == 0) {// set state to 1, ExclusiveOwnerThread = thread2 and return true if (compareAndSetState(0, Acquires)) {setExclusiveOwnerThread(current); return true; Else if (current == getExclusiveOwnerThread()) {state = state +1 int nexTC = c + acquires; if (nextc < 0) // overflow throw new Error("Maximum lock count exceeded"); // Update state setState(nexTC); return true; } return false; }Copy the code

The logic of the above code is roughly as follows:

1. Get the current thread

2. Get the current state, since state is volatile and thread visibility is not a concern.

3, determine state==0, indicating that the lock is not held, set state to 1, set the lock holding thread to the current thread, return true to indicate that the lock is acquired.

4, the state! =0, determine whether the thread holding the lock is the current thread

5, is the current thread, state=state+1, return true to obtain the lock successfully.

If thread 1 comes in, it’s done with step 3.

addWaitermethods

Thread 1 holds the lock, sets state to 1, and when thread 2 returns false from the nonfairTryAcquire method above, the addWaiter method is invoked. Paste the acquire method above again:

Public final void acquire(int arg) {if (! tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)){ selfInterrupt(); }}Copy the code

AddWaiter (Node.exclusive) and acquireQueued(addWaiter(Node.exclusive), arg). Let’s use a sequence diagram to see where we are.

So what does this addWaiter method actually do?

static final Node EXCLUSIVE = null; // Because node. EXCLUSIVE=null, Private Node addWaiter(Node mode) {Node Node = new Node(thread.currentThread (), mode); //1 // tail=null Node pred = tail; if (pred ! = null) { node.prev = pred; if (compareAndSetTail(pred, node)) { pred.next = node; return node; }} // If you fail in the previous step, you can join the team through ENQ. enq(node); return node; }Copy the code
Enq method
Private Node enq(final Node Node) {//CAS" spins "until it joins the queue for (;;) { Node t = tail; If (t == null) {//CAS" spin "until successfully join the queue if (compareAndSetHead(new Node())) tail = head; } else {//CAS" spins "until it joins the queue. if (compareAndSetTail(t, node)) { t.next = node; return t; }}}}Copy the code
acquireQueuedmethods

This method is implemented by AQS, which is a complex method that continuously tries one of the following two operations on the Node just enqueued above.

  • Continue trying to acquire the lock while the precursor node is the head node

  • Suspends the current thread so that the CPU no longer schedules it

The thread failed to acquire the resource and has been placed at the end of the queue. You should immediately know what the thread should do next: go into the wait state and rest until the other thread has completely released the resource, wake up, get the resource yourself, and then go do what you want. Yes, that’s it! AcquireQueued () acquireQueued() : waits in a queue to get a number (with nothing else to do) until it gets a number and then returns. This function is very critical, continue to look at the source:

Final Boolean acquireQueued(final Node Node, int arg) {final Boolean failed = true; Boolean interrupted = false; // Another spin! for (;;) {// Final Node p = node.predecessor(); // If the head is already the second node, then it is eligible to attempt to fetch resources. If (p == head && tryAcquire(arg)) { So head refers to the benchmark node, // is the node where the resource is currently fetched or null. setHead(node); // node.prev is null in setHead, and head.next is null in setHead. It means that the node out of the team before taking the resource! p.next = null; failed = false; // Get resources successfully. } // If you are ready to rest, enter the waiting state through park() until unpark(). If the uninterruptible condition is interrupted, // then it wakes up from park(), finds that the resource is not available, and continues into park() to wait. if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()) interrupted = true; }} Finally {// If the node has been interrupted (for example, timeout, or interrupted in the case of interruptible), then the node is interrupted from the queue. if (failed) cancelAcquire(node); }}Copy the code
ShouldParkAfterFailedAcquire method

The main purpose of this method is to check the status and see if you really can go to rest, in case the thread in front of the queue gives up and just stands there blind, right?

private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) { int ws = pred.waitStatus; If (ws == node. SIGNAL) return true; If (ws) > 0 {/ * * if the precursor to give up, then go straight to find, wait until recently found a normal state, side by side in the back of it. * Note: the abandoned nodes, because they are "pushed" in front of them, form a no-reference chain and will be removed by the security uncle later (GC collection)! */ do { node.prev = pred = pred.prev; } while (pred.waitStatus > 0); pred.next = node; } else {// If the precursor works well, set the precursor status to SIGNAL and tell it to notify itself when it finishes retrieving the number. It could fail. He might have just been released! compareAndSetWaitStatus(pred, ws, Node.SIGNAL); } return false; }Copy the code

During the whole process, if the state of the precursor node is not SIGNAL, then I cannot rest at ease. I need to find a rest point and try again to see if it is my turn to get the number.

parkAndCheckInterrupmethods

Once the thread has found a safe rest point, it can rest in peace. The idea is to let the thread rest and really wait. It’s called the Park method

private final boolean parkAndCheckInterrupt() { LockSupport.park(this); // Call park() to put the Thread into a waiting state. Return thread.interrupted (); // If you are awakened, check to see if you are interrupted. }Copy the code

Park () leaves the current thread in a waiting state. There are two ways to wake up the thread in this state: 1) unpark(); 2) Interrupt (). Note that Thread.interrupted() clears the interrupt flag bit of the current Thread.

acquireQueuedMethods to summarize
  1. After the node enters the end of the team, check the status and find the safe rest point;

  2. Call park() to enter a waiting state, waiting for unpark() or interrupt() to wake up.

  3. After being awakened, to see if they are eligible to get the number. If taken, head points to the current node and returns whether the whole process from joining the queue to getting the number was interrupted; If not, proceed to procedure 1.

selfInterruptmethods

This method is implemented by AQS and is used to interrupt the current thread. Since we do not respond to interrupts during the whole lock grab process. What if there’s a break in the lock grab? You can’t just ignore it. AQS simply records whether or not an interrupt has occurred, and if returned, calls selfInterrupt before exiting the acquire method, as if the interrupt occurred during the lock grab had been “deferred” until after the lock grab.

tryAcquireMethods to summarize

Post the source code for the tryAcquire method again

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

Copy the code

Acquire the process

  1. Call tryAcquire() on the custom synchronizer to attempt to acquire the resource directly and return it if successful;

  2. If not, addWaiter() adds the thread to the end of the wait queue and marks it as exclusive;

  3. AcquireQueued () causes the thread to rest in the waiting queue and attempt to acquire the resource when it has an opportunity (unpark() in its turn). Return after obtaining the resource. Returns true if it was interrupted during the entire wait, false otherwise.

  4. If a thread has been interrupted while waiting, it does not respond. SelfInterrupt () is performed only after the resource has been retrieved, reclaiming the interrupt.

Since this function is the most important, LET me summarize it with a flow chart:

At this point, the acquire() process finally comes to an end. ReentrantLock: acquire(1); acquire(1); acquire(1); acquire(1); acquire(1);

It’s not easy. It’s just a lock method.

An unfair lock occupies the entire flow of the lock

Fair lock

static final class FairSync extends Sync { private static final long serialVersionUID = -3000897897090466540L; Final void lock() {acquire(1); } hasqueuedprotected 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; }} // If the current thread is at the top of the queue or the queue is empty, Public final Boolean hasqueuedestablishes () {// The correctness of this depends on head being initialized // before tail and on head.next being accurate if the current // thread is first in queue. Node t = tail; // Read fields in reverse initialization order Node h = head; Node s; return h ! = t && ((s = h.next) == null || s.thread ! = Thread.currentThread()); }Copy the code

The main difference between a fair lock and an unfair lock is that a fair lock takes into account whether there are threads in the queue before it, i.e. whether there are threads in the queue before it.

Deep unlock method

It doesn’t matter whether the lock is released or fair. Continue with the unlock method in ReentrantLock

private final Sync sync;
public ReentrantLock() {
        sync = new NonfairSync();
}
public void unlock() {
        sync.release(1);
}

Copy the code

The release method here is the release method in AQS, which is the top-level entry for threads to release shared resources in exclusive mode. It frees a specified amount of resources, and if it frees completely (i.e., state=0), it wakes up other threads in the waiting queue to acquire resources. That’s the semantics of unlock(), but it’s not just unlock().

Here’s the source code for release() :

Public final Boolean release(int arg) {if (tryRelease(arg)) {Node h = head; if (h ! = null && h.waitStatus ! = 0) unparkSuccessor(h); return true; } return false; } / / empty methods, leaving a subclass to implement protected Boolean tryRelease (int arg) {throw new UnsupportedOperationException (); }Copy the code

The tryRelease method here is implemented by Sync, a subclass of AQS, which is the parent of fair lock and non-fair age

// Release the lock currently occupied by the thread releases releases=1 protected final Boolean tryRelease(int Releases) {// Calculate state=state-1 int c = getState() - releases; If (thread.currentThread ()! = getExclusiveOwnerThread()) throw new IllegalMonitorStateException(); boolean free = false; If (c ==0) {free = true; // Set the lock holding thread to null setExclusiveOwnerThread(null); // setState to 0 setState(c); return free; }Copy the code

Releasing locks is fairly straightforward. This is the end of the lock release.

Timeout acquisition lock

The tryLock(long timeout, TimeUnit Unit) function of ReetrantLock is provided. The semantics are true if the lock was acquired within the specified time, false if it was not. This mechanism prevents threads from waiting indefinitely for locks to be released.

Continue to look at the source code is how to achieve

Public Boolean tryLock(long timeout, TimeUnit unit) throws InterruptedException { return sync.tryAcquireNanos(1, unit.toNanos(timeout)); }Copy the code

The tryAcquireNanos method is a method in AQS

public final boolean tryAcquireNanos(int arg, Long nanosTimeout) throws InterruptedException {if (thread.interrupted ()) throw new InterruptedException(); // return tryAcquire(arg) || doAcquireNanos(arg, nanosTimeout); }Copy the code

We talked about the tryAcquire method earlier. This is no longer a drag.

Private Boolean doAcquireNanos(int arg, long nanosTimeout) throws InterruptedException { If (nanosTimeout <= 0L) return false; Deadline final Long Deadline = system.nanotime () + nanosTimeout; Node final node node = addWaiter(node.exclusive); boolean failed = true; Try {// Infinite loop attempts to get -- spin for (;;) Final Node p = node.predecessor(); // if (p == head && tryAcquire(arg)) {// if (p == head && tryAcquire(arg)) {// if (p == head && tryAcquire(arg)) {// if (p == head && tryAcquire(arg)) {// if (p == head && tryAcquire(arg)) { SetHead (node); // GC p.ext = null; failed = false; return true; } // Calculate how much time is left nanosTimeout = deadline-system.nanotime (); If (nanosTimeout <= 0L) return false; if (shouldParkAfterFailedAcquire(p, node) && nanosTimeout > spinForTimeoutThreshold) LockSupport.parkNanos(this, nanosTimeout); If (thread.interrupted ()) throw new InterruptedException(); if (thread.interrupted ()) throw new InterruptedException(); } } finally { if (failed) cancelAcquire(node); } } private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) { int ws = pred.waitStatus; If (ws == node. SIGNAL) return true; If (ws) > 0 {/ * * if the precursor to give up, then go straight to find, wait until recently found a normal state, side by side in the back of it. * Note: the abandoned nodes, because they are "bumped" in front of them, form a referenceless chain and are later removed by the security uncle (GC collection)! */ do { node.prev = pred = pred.prev; } while (pred.waitStatus > 0); pred.next = node; } else {// If the precursor works well, set the precursor status to SIGNAL and tell it to notify itself when it finishes retrieving the number. // I may fail, I may just be released! compareAndSetWaitStatus(pred, ws, Node.SIGNAL); } return false; } private static final boolean compareAndSetWaitStatus(Node node,int expect,int update) { return unsafe.compareAndSwapInt(node, waitStatusOffset, expect, update); }Copy the code
Summary of timeout acquisition lock

If the timeout period is less than or equal to 0, the system returns failure. The line enters the queue, spins, tries to acquire the lock, returns on success, and suspends itself at a safe point in the queue until the timeout expires.

A lot of people might ask: Why do we need a loop here?

Because the precursor state of the current thread node may not be SIGNAL, the thread will not be suspended in the current loop, and then the timeout will be updated to start a new attempt

The last

Well, here we share today’s content is over, if you have any questions, or some knowledge in the article is not very clear, you can add my wechat, we moved a bench to sit and chat.

reference

Segmentfault.com/a/119000001… Blog.csdn.net/u010452388/… www.cnblogs.com/waterystone…

Look forward to your likes, views and shares!

Recommended reading

  • If you don’t move, I won’t move — observer mode

  • Interview summary of 4 companies in 10 days after resignation

  • Spring Cloud Alibaba video tutorial

  • Backend developers, learning the way

  • Back end interview 230,000, more than 500 pages, awesome!

  • 112 interview summary, suggestions received