当前位置:网站首页>AQS - detailed explanation of reentrantlock source code

AQS - detailed explanation of reentrantlock source code

2022-06-13 07:21:00 Focus on writing bugs

What is? AQS

AQS Is in Java in AbstractQueuedSynchronizer Abbreviation . What he means is Abstract queue synchronizer .

stay JUC(java.util.concurrent) in , Many lock related operation classes , Will depend on him . For example, the following blog needs to say ReentrantLock.

in the light of ReentrantLock Simple use , You can refer to previous blogs java.util.concurrent.locks.Lock lock .

AQS What subclasses are there

stay java.util.concurrent.locks.AbstractQueuedSynchronizer In this class , according to IDEA It can generate its subclass information more intuitively . As shown below :
 Insert picture description here
【 Expand :】 About how to generate ?

IDEA—— How to view other subclasses of a class

Before interpreting the source code, you need to understand

1、 What is? CAS?

The content is more , Wrote an article independently , You can refer to the following address
CAS Understand the principle of

2、 What is fair lock ? What is unfair lock ?

Fair and unfair , Depends on when the lock is released by the previous thread , Whether the new thread can take the lock immediately .

1、 Let's start with T0、T1、T2 Need to get lock , Lock operation , But this time T0 Lock acquired successfully , that T1 and T2 You need to wait , wait for T0 After releasing the lock, obtain the lock .
2、 If another thread comes at this time T3, And this is the right time T0 Lock release successful ;

fair :T3 Line up backwards .
Unfair : You may not consider queuing , Directly participate in lock contention .

3、 About thread wakeup .

About thread sleep and wakeup , There are multiple ways to achieve .

  • sleep(xx)
    sleep(long miilis) The method is Thread Class static methods , Called in code Thread.sleep(timeout) Method , Causes the operating system to suspend the current thread , call Thread.sleep(timeout) The following changes will occur after the method :

    • The state of the thread changes to TIMED_WAITING
    • Called sleep After the method ,timeout Only when the thread is exhausted can it re-enter the executable state
    • If in syncronized Code block Thread.sleep(timeout),Monitor The lock Owner There will be no switching , That is to say, the thread calls sleep Method does not release the lock
  • wait
    And sleep The method is different ,wait It belongs to Object Class method ,JDK Two commonly used in wait Method , namely wait() and wait(long timeout). from Monitor It is not difficult for us to draw the following conclusion about the principle of lock :

    • wait Method must be in synchronized Code block ( Or used syncronized Methods of keyword modification )
    • Thread calls wait Method will release the lock , Get into Monitor Lock object's WaitSet
    • Thread calls wait The method will become Wating state
    • Called wait() The thread of the method will always be in Waiting state , until Monitor Object's Owner Called notify perhaps notifyAll Method .notify Method will wake up randomly WaitSet One thread in , and notifyAll Will wake up WaitSet All threads in
    • wait(long timeout) It will also wake up automatically when the waiting time is exhausted
  • park/unpark
    park/unpark The function of is a bit similar to wait/notify, All pauses / Wake up the thread . He is java.util.concurrent.locks.LockSupport Next related Static methods .

    LockSupport.park();
    LockSupport.unpark( Thread pointing ); //  Wake up the specified thread 
    
  • notify、notifyAll
    notify/notifyAll() The method is Object The local final Method , Can't be rewritten .

    notify Wake up operation of , have Randomness .

【 mark :】

Above Thread Sleep and wake up Reference material :
Java Multithreading learning wait、notify/notifyAll Detailed explanation
java Concurrent learning - Thread sleep and wake up

ReentrantLock Source code interpretation

ReentrantLock Class structure

stay java.util.concurrent.locks.ReentrantLock Source code ,ReentrantLock Is defined as follows :

public class ReentrantLock implements Lock, java.io.Serializable

Inheritance from classes , It is not found whether it is related to AbstractQueuedSynchronizer(AQS) Related . Actually ReentrantLock The core of is its sync Attribute information .

private final Sync sync;

ReentrantLock Internal abstract class .

abstract static class Sync extends AbstractQueuedSynchronizer {
    
    private static final long serialVersionUID = -5179523762034025860L;

    /** * Performs {@link Lock#lock}. The main reason for subclassing * is to allow fast path for nonfair version. */
    abstract void lock();

    /** * Performs non-fair tryLock. tryAcquire is implemented in * subclasses, but both need nonfair try for trylock method. */
    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;
    }

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

    protected final boolean isHeldExclusively() {
    
        // While we must in general read state before owner,
        // we don't need to do so to check if current thread is owner
        return getExclusiveOwnerThread() == Thread.currentThread();
    }

    final ConditionObject newCondition() {
    
        return new ConditionObject();
    }

    // Methods relayed from outer class

    final Thread getOwner() {
    
        return getState() == 0 ? null : getExclusiveOwnerThread();
    }

    final int getHoldCount() {
    
        return isHeldExclusively() ? getState() : 0;
    }

    final boolean isLocked() {
    
        return getState() != 0;
    }

    /** * Reconstitutes the instance from a stream (that is, deserializes it). */
    private void readObject(java.io.ObjectInputStream s)
        throws java.io.IOException, ClassNotFoundException {
    
        s.defaultReadObject();
        setState(0); // reset to unlocked state
    }
}

It's about fair and Unfair In its Inner subclass There are related definitions in .

//  Unfair 
static final class NonfairSync extends Sync {
    
    private static final long serialVersionUID = 7316153563782823691L;

    //  Lock mode 
    final void lock() {
    
        if (compareAndSetState(0, 1))
            setExclusiveOwnerThread(Thread.currentThread());
        else
            acquire(1);
    }

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

//  fair 
static final class FairSync extends Sync {
    
    private static final long serialVersionUID = -3000897897090466540L;

    final void lock() {
    
        acquire(1);
    }

    /** * 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 (!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;
    }
}

AbstractQueuedSynchronizer structure

Because in java.util.concurrent.locks.ReentrantLock Define a Abstract inner classes java.util.concurrent.locks.ReentrantLock.Sync, Where the inner class Realization AbstractQueuedSynchronizer This abstract class .

java.util.concurrent.locks.AbstractQueuedSynchronizer The inheritance relationship of the class is as follows :

public abstract class AbstractQueuedSynchronizer
    extends AbstractOwnableSynchronizer
    implements java.io.Serializable 

As can be seen from the inheritance relationship ,java.util.concurrent.locks.AbstractQueuedSynchronizer yes AbstractOwnableSynchronizer Subclasses of , stay AbstractOwnableSynchronizer Parent class in , only one java.util.concurrent.locks.AbstractOwnableSynchronizer#exclusiveOwnerThread Declaration of attributes , And provided with get/set Method .

Used to save the specific thread object reference .

stay AbstractQueuedSynchronizer Subclass in , Defined as Node Inner class , Used to save specific data information , Its structure is as follows :
 Insert picture description here
【 mark :】

AQS Mainly for the preservation of Which thread holds the lock This basic information .
Second, keep Others have not been unlocked The queue where the thread is located Team leader and A party The direction of .

AQS The synchronization waiting queue in is also called CLH queue ,CLH The queue is Craig、Landin、Hagersten One invented by three people is based on Two way linked list data structure Of queue , yes FIFO First in, first out threads wait for the queue ,Java Medium CLH The original queue is CLH A variant of the queue , Thread from the original spin mechanism to block mechanism .

 Insert picture description here
【 doubt :】 Why should it be designed as a two-way linked list ?

Bidirectional has the advantage of fast searching in different directions .

【 doubt :】 Two way linked list data Node Under the class thread What does the attribute point to ?

Lock not obtained , Thread object information that needs to be queued .

ReentrantLock Object creation

ReentrantLock There are two ways to create objects :

public ReentrantLock() {
    
  sync = new NonfairSync();
}

public ReentrantLock(boolean fair) {
    
    sync = fair ? new FairSync() : new NonfairSync();
}

from Construction method It can be seen that :

Take... Directly new ReentrantLock() By default, a Unfair NonfairSync object .

Lock operation

With Fair lock For example , Create a fair lock in the following ways :

Lock lock = new ReentrantLock(true);

Source code is as follows :
 Insert picture description here

The locking operation is :

lock.lock();

see Fair lock Of Lock method , Its logic has the following definitions :

final void lock() {
    
    acquire(1);
}

call acquire() And pass on a Constant 1.
Continue to explore down , The specific locking operation is as follows :

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

This code is integrated , Split it into code that is easy to identify , As shown below :

if (!tryAcquire(arg){
    
	if(acquireQueued(addWaiter(Node.EXCLUSIVE), arg)){
    
		selfInterrupt();
	}
}

Next, the logic implementation is analyzed :

  • tryAcquire: Attempt to acquire lock
    In concrete java.util.concurrent.locks.ReentrantLock.FairSync In subclass , There are the following ways to tryAcquire A copy of , The code is shown below :

    	protected final boolean tryAcquire(int acquires) {
          
    		// 1、 Get the object of the current execution thread 
            final Thread current = Thread.currentThread();
            // 2、 obtain  AbstractQueuedSynchronizer  in  state  The attribute value 
            int c = getState();
            // 3、 If the status information of the current state holder is 0
            if (c == 0) {
          
            	// 4、 by 0 This does not mean that no other thread is locked 
            	//  You need to think about ,t0 The thread just released the lock , stay AbstractQueuedSynchronizer  Whether there are other threads queued in the queue 
            	// !hasQueuedPredecessors()  Indicates that there are no other threads in the queue to perform queueing operations 
            	//  When judging  AQS  When there are no other threads queued in the queue , At this point, you try to obtain the lock 
            	// compareAndSetState  take  CAS  Algorithm ( It's atomic ), To obtain the lock , And will AQS Medium state Make changes 
                if (!hasQueuedPredecessors() &&
                    compareAndSetState(0, acquires)) {
          
                    // 5、 If there are no other threads in the queue waiting , And take CAS The algorithm obtains the lock successfully , Will AQS Medium  exclusiveOwnerThread  Set to reference of current thread 
                    //  Indicates that the current thread holds a lock 
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            // 6、 Considering non 0 The situation of 
            //  Determine the current thread and AQS Stored in the  exclusiveOwnerThread  Whether the information matches 
            //  If the two match , It indicates that the lock is held by the current thread , So you can directly put AQS Medium  state  Conduct  +1 operation .
           	//  Determine whether the current thread repeatedly locks  ( Reentrancy )
            else if (current == getExclusiveOwnerThread()) {
          
                int nextc = c + acquires;
                if (nextc < 0)
                    throw new Error("Maximum lock count exceeded");
                //  modify AQS Medium state Status value information , Multiple calls lock() To lock , Accumulate them 
                //  Be careful :state Only for  0  The situation of , To allow other threads to acquire locks !
                setState(nextc);
                return true;
            }
            // 7、 If the current lock holder is not the current thread , Then return to false, Indicates failed to acquire lock , Go to the next operation 
            return false;
        }
    

    if (c == 0) situations , The processing operations are as follows :
     Insert picture description here

    if (c != 0) And current == getExclusiveOwnerThread() when , The processing operations are as follows :
     Insert picture description here

    【 doubt :】 Why does it show up state Not for 0 The situation of ?

    In the method, it may appear that each reference method also contains lock.lock() and lock.unlock() Methods . But the method refers to other methods. On the whole, it belongs to the same thread repeatedly locking it . That is to say Lock reentry .
    As shown below :
     Insert picture description here
    At this point, execute before the business code , For the same thread two ( Or many times ) Lock operation .

       The code to judge whether there are other queues in the queue is as follows :
    
        public final boolean hasQueuedPredecessors() {
          
            // 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());
                //  Queued AQS In the data structure , head head And tail tail All of them point to null when ,
                //  Means nothing else Node node , That is, there are no other waiting threads in the queue 
        }
    
  • acquireQueued: Try to queue a thread object
    When the program is executed tryAcquire(arg) Return to false when , Express Current thread failed to acquire lock . here !tryAcquire(arg) It's true , Save the current thread object to the queue , The source code logic is as follows :

    acquireQueued(addWaiter(Node.EXCLUSIVE), arg)
    

    Current Try to join the queue It is divided into two parts .

    First step addWaiter(Node.EXCLUSIVE);
    The second step acquireQueued(addWaiter(Node.EXCLUSIVE), arg).

    among addWaiter(Node.EXCLUSIVE) The operation logic of is as follows :

    private Node addWaiter(Node mode) {
          
    	//  Create a Node node , Save the reference and state information of the current thread 
    	// mode Divided into two :Node.EXCLUSIVE  Mutually exclusive (ReenTrantLock characteristic );Node.SHARED share (Semaphore characteristic )
        Node node = new Node(Thread.currentThread(), mode);
        // Try the fast path of enq; backup to full enq on failure
        //  Get the end of the queue 
        Node pred = tail;
        
        if (pred != null) {
          
        	//  If CAS There are other Node object , Will be new node Class prev The node points to this Node
            node.prev = pred;
            //  At the same time take CAS The algorithm sets the original queue , take tail Property value is set to new node object 
            if (compareAndSetTail(pred, node)) {
          
            	//  Last in the original queue node Object's next Property points to the new last node object 
                pred.next = node;
                return node;
            }
        }
        
        //  When in the queue ,tail When there is no point on the property ( Empty queue ); Or there is a team leader but a new one is added Node To queue failed 
        //  This method has two uses :
        // 1、 Build queue headers , Prevent null pointer problems 
        // 2、 New node The node is inserted into the queue , Until the insertion is successful 
        enq(node);
        return node;
    }
    private Node enq(final Node node) {
          
    	//  Dead cycle , Infinite spin 
    	//  Ensure that any thread that does not get the lock can be successfully queued 
        for (;;) {
          
        	//  Get no nodes 
            Node t = tail;
            // AbstractQueuedSynchronizer  Medium head and tail attribute , Point to the two-way linked list , That is to say CLH The head and the tail of the line 
            //  When there is data in the queue ,head and tail Property will not be null
            if (t == null) {
           // Must initialize
            	//  When it comes to null, Indicates that there is no data information in the queue , That is, the queue has no 
            	//  You need to use CAS The algorithm creates a “ Empty Node” node , And set it to AQS Medium head Go up 
                if (compareAndSetHead(new Node()))
                	//  hold  AbstractQueuedSynchronizer  Of  tail attribute   Also set this “ Empty Node” node 
                    tail = head;
                    //  There's only one in the queue “ Empty Node” Node time ,head and tail attribute   All point to   This “ Empty Node” node 
                    //  Enter next cycle 
                    
            } else {
          
            	//  Get the tail of the current queue Node Node object 
            	//  Here is the new... That needs to be added to the queue node object .
            	//  New node Object's prev Property points to the last one in the previous queue node
                node.prev = t;
                // CAS Algorithm , Reset the queue tail attribute , For those who need to be added node object 
                if (compareAndSetTail(t, node)) {
          
                	//  The last one in the old queue node Of next Property points to the new node object , Realize the operation of joining the team 
                    t.next = node;
                    return t;
                }
            }
        }
    }
    

    for There is... In the loop return, Will be terminated for loop !

    enq(final Node node) Method The execution logic is as follows :
     Insert picture description here
     Insert picture description here

    addWaiter(Node mode) in , Judge from the beginning Node pred = tail There is != null situations , Its treatment is as follows :

    Let's say the thread T2 Get into .

     Insert picture description here

    When Lock not obtained The thread of , Successful entry queue ( Double linked list ) after , Here, we will proceed to the next processing method :

    acquireQueued(addWaiter(Node.EXCLUSIVE), arg)
    

    The processing logic of this method is as follows :

     final boolean acquireQueued(final Node node, int arg) {
          
        boolean failed = true;
        try {
          
            boolean interrupted = false;
            //  Spin operation , It must be ensured that threads can block 
            for (;;) {
          
            	//  Get current node Object's prev Property value , In other words, the queue should node The last one of node object 
                final Node p = node.predecessor();
                //  If it's time to node object  p  And AQS Medium head matching ( Queue header ), Then try to get the lock again 
                //  because : Structure of double linked list , The head of the team is a  “ Empty Node object ”
                //  Try not to block your threads ( Switching between kernel state and user state , Time consuming and inefficient )
                if (p == head && tryAcquire(arg)) {
          
                	//  If you get the lock , Then the previous node goes out of the queue 
                	//  take AQS Of head The header node is set to the current node
                	//  The previous in the queue Node Object thread Property is set to null( Because of this Node The thread in gets the lock )
                	
                	//  To make a long story short :
                	//  The thread object gets the lock , There is no need to remain in the queue ;
                	//  Put it Node Object thread Attribute is set to null, Become a ” empty Node node “, To be the new team leader 
                    setHead(node);
                    //  Put the old leader in the queue next clear ( Because the new node Set up to be the team leader , The previous team leader needed gc)
                    p.next = null; // help GC
                    failed = false;
                    return interrupted;
                }
                //  If you don't get the lock , You need to do the following ---- Blocking threads 
                // shouldParkAfterFailedAcquire  Set up Node Nodes in the waitStatus Property value 
                // parkAndCheckInterrupt  call  LockSupport.park( Threads ), Block the current thread 
    			
    			//  Because of the Node object ,waitStatus  The default value is 0;
    			// 1、 First, the first cycle , If  waitStatus  The value of is default 0; 
    				//  modify head state , take  waitStatus  It is amended as follows  sinal(-1), So that it can be awakened , But what it returns is false
    			// 2、 The next cycle , Judged  waitStatus  Status as  sinal(-1), return true, Indicates that execution can begin  parkAndCheckInterrupt()  Blocking threads 
    				//  At the same time, determine whether the thread is awakened by an interrupt signal !
    
    			// Be careful :
    			 	//  If at first  waitStatus  It's worth it  > 0 ( Cancelled status ), Then the drive node is abolished 
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
          
            if (failed)
                cancelAcquire(node);
        }
    }
    //  According to the last Node node , Determine the next... In the queue Node Whether the node can be awakened 
    //  Parameter one : At present node In the object prev Property value , The last one Node Object node 
    //  Parameter two : At present node Object node 
    private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
          
    	//  Get previous node Nodes in the  waitStatus  value 
        int ws = pred.waitStatus;
        //  If meet “ Can be awakened ” The state of 
        if (ws == Node.SIGNAL)
            //  return true
            return true;
        if (ws > 0) {
          
           	//  If the state of the precursor node is cancelled , Will be removed from the queue 
            do {
          
                node.prev = pred = pred.prev;
            } while (pred.waitStatus > 0);
            pred.next = node;
        } else {
          
            //  Current drive node waitStatus by  0 or PROPAGATE In the state of 
            //  Set it to SIGNAL(-1) state , Then the current node can be safely park
            //  Outside is a spin operation 
            compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
        }
        return false;
    }
    
    private final boolean parkAndCheckInterrupt() {
          
    	//  Use  LockSupport  Let the current Node Thread in thread Blocked 
        LockSupport.park(this);
        return Thread.interrupted();// Returns whether the thread has been interrupted 
    }
    

    【 Be careful :】shouldParkAfterFailedAcquire(Node pred, Node node) Means :

    The head of the line ( That is to say “ Empty Node object ”) Medium waitStatus What is kept is Really effective next Node node Of State information .
     Insert picture description here

    if (p == head && tryAcquire(arg)) When the conditions are met , The execution logic is as follows :
     Insert picture description here

    【 Expand :】java.util.concurrent.locks.AbstractQueuedSynchronizer.Node#waitStatus The meaning of several values :

    • SIGNAL:-1 Can be awakened

      The thread of the relay node is in a waiting state , If the current node releases the synchronization state or is cancelled ,
      Subsequent nodes will be notified , Enables subsequent nodes to run threads .

    • CANCELLED:1 Represents an exception , Caused by interruption , Need to end

      The thread waiting in the synchronization queue timed out or was interrupted , Need to cancel waiting from synchronization queue

    • CONDITION:-2 Conditions wait

      The node is in the waiting queue , The thread of the node is waiting in Condition On ,
      When other threads Condition Called signal() After the method , The node moves from the waiting queue to the synchronization queue , Add to get synchronization state

    • PROPAGATE:-3 spread

      Indicates that the next shared synchronization state acquisition will be unconditionally propagated

    • 0: Initialization status value

Release the lock and wake up the next thread

stay Java In the code , When a thread has finished executing , Need developers in finally Lock in code block Release operation . Use the following code to achieve :

 lock.unlock();

among , The execution logic of this method is as follows :

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

Continue to drill down , It is known that the specific implementation is :

public final boolean release(int arg) 
	//  take  AQS  Medium  state  Modify the attribute value 
	//  If  AQS  Medium state The attribute value becomes 0, Then we need to put  exclusiveOwnerThread  Empty the attribute value 
	//  If meet  state  Become 0, And  exclusiveOwnerThread  Also get empty ,( That is, the successful release of the lock )
    if (tryRelease(arg)) {
    
    	//  that , From the two-way linked list ( queue ) In order to get head Property points to Node node 
        Node h = head;
        //  According to the head of the team Node nodes  waitStatus  The status value determines the next valid Node Whether the node can be awakened 
        //  stay Node When creating nodes ,waitStatus  The default data is 0
        //  In locking operation  shouldParkAfterFailedAcquire(Node pred, Node node)  operation , Will change it to  sinal(-1)
        //  When the wakeable state is satisfied , Of course, there are other status messages 
        if (h != null && h.waitStatus != 0)
        	//  Conduct   Wake up operation 
            unparkSuccessor(h);
        return true;
    }
    return false;
}

protected final boolean tryRelease(int releases) {
    
	//  obtain AQS  Medium  state  Status value information , And subtract it 
	//  Here's a little bit of attention :
	//  When there is a lock operation , When the lock re enters , here AQS Medium state The value information is incomplete 1!
    int c = getState() - releases;
    
    //  Verify the current thread and AQS Stored in the  exclusiveOwnerThread  Is the data consistent 
    if (Thread.currentThread() != getExclusiveOwnerThread())
    	//  Throw an exception if it is inconsistent 
        throw new IllegalMonitorStateException();
    boolean free = false;
    //  When  AQS Medium state The property value is 0 when , To ensure that this thread “ Completely ” Lock released 
    //  Consider the reentry of the lock 
    if (c == 0) {
    
        free = true;
        //  take  AQS in exclusiveOwnerThread Set the value of the property to null 
        setExclusiveOwnerThread(null);
    }
    //  take AQS Medium state The value is set to the new value 
    setState(c);
    return free;
}
//  Set up  AQS  Medium  exclusiveOwnerThread  attribute 
protected final void setExclusiveOwnerThread(Thread thread) {
    
    exclusiveOwnerThread = thread;
}
//  Wake up operation 
private void unparkSuccessor(Node node) {
    
  	//  Get the empty of team leader Node node , According to its  waitStatus  Judge the next Node Whether wakeup operation is supported 
    int ws = node.waitStatus;
    //  If   Its data information  <0 , Then use CAS The algorithm modifies its waitStatus value , Change it to 0
    //  Why is it changed back to 0 Well ? Refer to the following questions to explain !
    if (ws < 0)
        compareAndSetWaitStatus(node, ws, 0);
	
	//  Get the first air of the team Node The next valid... Of the node Node node ( Reference save of existing thread )
    Node s = node.next;
    //  If the next node does not exist , Or its  waitStatus  State information  >0 ( Greater than 0 Only  CANCELLED) Indicates an exception , Need to be abandoned 
    if (s == null || s.waitStatus > 0) {
    
        s = null;
        for (Node t = tail; t != null && t != node; t = t.prev)
        	//  Two way linked list from back to front  <= 0 Of Node node 
        	//  That is, traverse forward from the back tail to find the first node in the normal blocking state 
	       	//  Wake up 
            if (t.waitStatus <= 0)
                s = t;
    }
    if (s != null)
    	//  Get what can be awakened Node Saved in node  thread 
    	//  namely : Wake up a thread that can be awakened ( It's a little tongue twister , Just know the meaning QAQ)
        LockSupport.unpark(s.thread); 
}

【 doubt :】 Why is it unparkSuccessor(Node node) In execution logic , There will be When node.waitStatus < 0 when , Conduct compareAndSetWaitStatus(node, ws, 0) operation ?

1、 In the locking operation , When a thread executes to parkAndCheckInterrupt() when , here The thread will be interrupted .( Pause there !)
2、 If it is Fair lock , When Release the lock unparkSuccessor(Node node) After execution , Corresponding Specify... In the queue Node node Of thread A thread will be awakened !
Because of the locked parkAndCheckInterrupt() Exist in Dead cycle ( spinlocks ) in , At this point, after the specified thread is awakened , It's going to be Locking logic if (p == head && tryAcquire(arg)) Judge .
If I get the lock , The queue and AQS Information in . If you don't get the lock , Will also waitstatus Value is modified to -1.
Fair lock , After the previous thread releases the lock , Qualified in the queue Node Medium thread You can definitely get the lock !

But if it is Not fair lock Well ?

1、 stay Not fair lock in , Every Node After object creation , Its waitStatus Property values are The default is 0.
2、 here The thread holding the lock released the lock , Qualified in the queue Node node Thread in , It is not always possible to acquire the lock immediately ! The lock may have been snatched away by a new thread outside !

therefore , Here in the source code , Make it compareAndSetWaitStatus(node, ws, 0) operation !

Logic diagram

 Insert picture description here

原网站

版权声明
本文为[Focus on writing bugs]所创,转载请带上原文链接,感谢
https://yzsam.com/2022/02/202202270549488770.html