当前位置:网站首页>Detailed analysis of abstractqueuedsynchronizer (AQS) source code - Analysis of lock release and response interrupt locking processes

Detailed analysis of abstractqueuedsynchronizer (AQS) source code - Analysis of lock release and response interrupt locking processes

2022-06-21 08:32:00 *Wucongcong*

AQS Member method resolution - Release lock logic

1、unlock() Method : Release the lock

//  be located ReentrantLock Class : Method of releasing the lock 
public void unlock() {
    
    // Release the lock 
    sync.release(1);
}

//  be located AQS The static inner class of Sync in : How to actually release the lock 
// RentrantLock.unlock() -> sync.release()
public final boolean release(int arg) {
    
    // tryRelease Try to release the lock :
    // true:  The current thread has completely released the lock 
    // false: The current thread has not fully released the lock 
    if (tryRelease(arg)) {
    
        // head  Under what circumstances will it be created ?
        //  When the lock thread does not release the thread , When another thread wants to acquire the lock during the lock holding period , Other threads found that they could not acquire the lock ,
        //  And the blocking queue is empty , At this point, subsequent threads will build a for the current lock thread head node ( Encapsulate the locking thread into head)
        //  Subsequent threads are then appended to head After the node ( Become head My back drive )
        Node h = head;
        
        //  Conditions 1:h != null establish , Describe the... In the queue head The node has been initialized ,ReentrantLock During use , There is too much thread contention ~
        //  Conditions 2:h.waitStatus != 0  establish , Show the current head It must have been inserted in the back node node ~
        if (h != null && h.waitStatus != 0)
            //  Wake up the rear drive node ~
            unparkSuccessor(h);
        return true;
    }
    return false;
}

2、tryRelease(int releases) Method : Try to release the lock

// RenntrantLock  Static inner class in Sync Medium  tryRelease() Method 
protected final boolean tryRelease(int releases) {
    
    //  Minus the released value ..
    int c = getState() - releases;
    //  Conditions established : Indicates that the current thread is not locked , Throw an exception directly 
    if (Thread.currentThread() != getExclusiveOwnerThread())
        throw new IllegalMonitorStateException();
    //  So let's go over here , It indicates that the current thread is the thread holding the lock 

    // free: Indicates whether the lock has been completely released , The default is  false
    boolean free = false;

    //  Conditions established : This indicates that the current thread has reached the condition of completely releasing the lock  c == 0
    if (c == 0) {
    
        free = true;
        setExclusiveOwnerThread(null);
    }
    //  to update  state value   There is no concurrency , So no need CAS  operation , Because only the thread holding the lock can execute here 
    setState(c);
    return free;
}

3、unparkSuccessor(Node node) Method : The method of waking up the first node that can be awakened among the successor nodes of the current node

// AQS Medium  unparkSuccessor  Method 
/** *  Wake up the next node of the current node  * @param node */
private void unparkSuccessor(Node node) {
    

    //  Get the status of the current node 
    int ws = node.waitStatus;

    if (ws < 0) // -1 SIGNAL state 
        //  Change to  0  Why : Because the current node has completed the task of waking up the subsequent nodes 
        compareAndSetWaitStatus(node, ws, 0);

    //  The first successor node of the current node 
    Node s = node.next;

    // s  When is equal to  null?
    // 1. The current node is  tail  Node time , s == null, Because in addWiter() Team entry method , There are three steps ,1. Set the... Of the current node prev Point to tail node 
    // 2. Set the current node cas Become  tail node  3. The original queue's  tail The node points to the current node , When the third step has not been carried out , The front node releases the lock , Called unparkSuccessor Method 
    //  This may lead to  s == null
    //  You need to find a node that can be awakened 

    //  Condition 2 :s.waitStatus > 0, precondition :s != null
    //  establish : Show the current node The successor node of the node is the cancel state , You need to find a suitable node to wake up 
    if (s == null || s.waitStatus > 0) {
    
        //  Find nodes that can be waked up 
        s = null;
        for (Node t = tail; t != null && t != node; t = t.prev)
            if (t.waitStatus <= 0)
                s = t;
        //  The above loop will find a distance from the current node The latest one can be awakened node,node You may not find ,node It could be null
    }
    //  If you find the right one to wake up  node, Wake up the node , Don't do anything if you can't find it 
    if (s != null)
        LockSupport.unpark(s.thread);
}

Last article :AbstractQueuedSynchronizer(AQS) Detailed analysis of source code - Lock competitive resource analysis and The earlier part of this article , It's all introductions ReentrantLock Of lock() Lock mode , This locking method is Not responsive to interrupts , Let's analyze Can respond to interruptions Lock mode of :lockInterruptibly()

Expand :AQS Member method resolution ( Response interrupt locking logic )

4、lockInterruptibly() A locking method that can be interrupted by a response

// be located ReentrantLock Class : A locking method that can be interrupted by a response 
public void lockInterruptibly() throws InterruptedException {
    
    // You can compete for resources in response to interruptions 
    sync.acquireInterruptibly(1);
}

//  be located AQS  Medium  acquireInterruptibly Method 
//  Compete for resources in response to interruptions 
public final void acquireInterruptibly(int arg)
    throws InterruptedException {
    
    //  If a thread is interrupted , Throws an exception directly 
    if (Thread.interrupted())
        throw new InterruptedException();
    //  If the contention lock resource fails, execute  doAcquireInterruptibly  Method 
    if (!tryAcquire(arg))
        doAcquireInterruptibly(arg);
}

//  be located AQS  Medium  doAcquireInterruptibly Method 
//  This method is executed when the contention lock resource fails 
private void doAcquireInterruptibly(int arg)
    throws InterruptedException {
    
    //  The current thread performs a queued operation 
    final Node node = addWaiter(Node.EXCLUSIVE);
    // true: Indicates that an interrupt exception occurred in the current thread 
    // false: Indicates that the current thread has successfully obtained the lock resource 
    boolean failed = true;
    try {
    
        //  Spin operation 
        for (;;) {
    
            //  Condition one holds : Indicates that the current node is head.next  node  head.next  Nodes have the right to compete for locks at any time 
            //  Condition 2 :tryAcquire(arg)
            //  establish : Explain the last one head  The corresponding thread has released the lock ,head.next  The thread corresponding to the node just gets the lock 
            //  Don't set up : explain  head  The corresponding thread has not released the lock yet  head.next  Still need to be park...
            if (p == head && tryAcquire(arg)) {
    
                //  What you need to do after you get the lock ?
                //  Set the current node to head node 
                setHead(node);
                //  The corresponding to the previous thread node.next The reference is set to null , Help the old head Out of the team 
                p.next = null; // help GC
                //  No exception occurred during lock acquisition of the current thread 
                failed = false;
                return;
            }

            // shouldParkAfterFailedAcquire(p, node): What is this method for ?  Does the current thread need to hang after it fails to acquire the lock spin ?
            //  Return value  true: Indicates that the current thread needs to be suspended  false: There is no need to suspend 
            // parkAndCheckInterrupt(): Prerequisite : The current thread needs to be suspended   What is the function of this method ?
            //  Suspends the current thread , And the interrupt flag of the current thread is returned after waking up 
            // ( Wake up the :1. Wake up normally   Other threads  unpark 2. Other threads will also wake up if they give an interrupt signal to the currently suspended thread )
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                //  If the current thread is awakened by an interrupt, an interrupt exception is thrown directly 
                throw new InterruptedException();
        }
    } finally {
    
        //  Conditions established : Indicates that the current thread failed to compete for lock resources but threw an interrupt exception 
        if (failed)
            //  Respond to the logic of leaving the queue 
            cancelAcquire(node);
    }
}

5、cancelAcquire(Node node) Method : Cancel the specified node Take part in the competition

// AQS  Medium  cancelAcquire() Method 
//  Respond to interrupt dequeue logic , Because there is no out of queue operation when an interrupt exception is thrown 
//  Cancel the specified  node  Take part in the competition 
private void cancelAcquire(Node node) {
    
    //  Empty judgment 
    // Ignore if node doesn't exist
    if (node == null)
        return;

    //  Because the queue has been cancelled , therefore node  The current thread of the internal association is set to null Just fine 
    node.thread = null;

    // Skip cancelled predecessors
    //  Get the current dequeue node The precursor node of 
    Node pred = node.prev;

    //  The main purpose of this process is to find a current node that is unqueued and then all the precursor nodes in the queue waitStatus<=0 The node of 
    while (pred.waitStatus > 0)
        node.prev = pred = pred.prev;

    //  Get the precursor next node 
    // 1. At present node node 
    // 2. It could be CANCELED node 
    Node predNext = pred.next;

    //  Will the current node The node status is set to cancel status  
    node.waitStatus = Node.CANCELLED;

    /** *  Currently unqueued node The location of the queue is different , Different exit strategies are implemented , Divided into 3 In this case : * 1. At present node It's the end of the team ,tail -> node * 2. At present node  No  head.next  node , Neither  tail node  * 3. At present node  yes head.next  node  */

    //  Condition one holds :node == tail, Show the current  node  It is the end of the queue node ,tail -> node
    //  Condition 2 :compareAndSetTail(node, pred)  establish : Explain the modification tail The node is pred node 
    if (node == tail && compareAndSetTail(node, pred)) {
    
        //  modify  pred.next -> null, complete  node  Out of the team 
        compareAndSetNext(pred, predNext, null);
    } else {
    

        //  Save the state of the node 
        int ws;

        //  The second case : At present node  No  head.next  node , Neither  tail node 
        //  Conditions for a :pred != head  establish : Indicates that the current node is not head.next node 
        //  Condition 2 :((ws = pred.waitStatus) == Node.SIGNAL || (ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL)))
        //  Conditions 2.1:(ws = pred.waitStatus) == Node.SIGNAL  establish : explain node The status of the precursor node of is  SIGNAL, Don't set up : The status of the precursor node may be 0
        //  In extreme cases : The precursor has also cancelled the queue 
        //  Conditions 2.2:(ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))  precondition : The state of the precursor node is not SIGNAL state 
        //  Suppose the precursor state is  <= 0 Set the precursor status to  SIGNAL  state , Indicates to wake up the successor node 
        // if What's done inside , Is to make pred.next -> node.next, So make sure that pred The node status is SIGNAL state 
        if (pred != head &&
            ((ws = pred.waitStatus) == Node.SIGNAL ||
                (ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) &&
            pred.thread != null) {
    
            //  situation 2: At present node No head.next node , Neither tail node 
            //  Out of the team :pred.next -> node.next After node , When node.next After being awakened , call shouldParkAfterFailedAcquire The method will make node.next Node crosses node in cancelled state 
            //  Complete the real departure 
            Node next = node.next;
            if (next != null && next.waitStatus <= 0)
                compareAndSetNext(pred, predNext, next);
        } else {
    
            //  The third case : At present node  yes head.next  node , Wake up its successor nodes directly 
            //  Similar situation 2: After the subsequent node is awakened , Would call  shouldParkAfterFailedAcquire  Ways to make node.next Cross the node in the cancelled state 
            //  The third node in the queue will be directly connected to head Establish a dual direction relationship 
            unparkSuccessor(node);
        }

        //  Point the subsequent pointer to itself , help GC
        node.next = node; // help GC
    }
}

summary

  • AQS Almost JAVA A basic framework for almost all locks and synchronizers in , here “ almost ”, Because very few did not pass AQS To achieve ;
  • AQS A queue is maintained in , This queue is implemented using a two-way linked list , Used to hold threads waiting for locks to queue ;
  • AQS A state variable is maintained in , Controlling this state variable can realize the lock and unlock operation ;
  • be based on AQS It is very easy to write a lock by hand , Just implement AQS Just a few ways .
原网站

版权声明
本文为[*Wucongcong*]所创,转载请带上原文链接,感谢
https://yzsam.com/2022/02/202202221454048025.html