Java concurrent class library AbstractQueuedSynchronizer Analysis

Source: Internet
Author: User

1. AQS Introduction

AQS is the basis of Java concurrent class libraries. It provides a basic framework based on FIFO queues that can be used to build locks or other related synchronization devices. The synx (hereinafter referred to as synx) uses an int to indicate the State and expects it to become the basis for most synchronization requirements. The method used is inheritance. The sub-classes manage their statuses by inheriting the synx and implementing it. The management method is to manipulate the status in a way similar to acquire and release. However, State control in a multi-threaded environment must ensure atomicity. Therefore, to control the state of sub-classes, you must use the following three methods provided by the synchronization tool to operate the state:

  • Java. util. concurrent. locks. AbstractQueuedSynchronizer. getState ()
  • Java. util. concurrent. locks. AbstractQueuedSynchronizer. setState (int)
  • Java. util. concurrent. locks. AbstractQueuedSynchronizer. compareAndSetState (int, int)

    Sub-classes are recommended to be defined as internal classes of custom synchronization devices. The synx itself does not implement any synchronization interfaces. It only defines several methods such as acquire for use. This synchronizator can be used as an exclusive mode or a shared mode. When it is defined as an exclusive mode, other threads will stop obtaining it, the sharing mode can be obtained successfully by multiple threads.

    The synx is the key to implementing the lock. It uses the synx to implement the lock semantics and then aggregates the synx in the implementation of the lock. It can be understood as follows: the lock API is intended for users. It defines the public behavior for interaction with the lock, and each lock needs to complete specific operations through these actions (for example: two threads can be allowed to lock and exclude more than two threads), but the implementation is based on the synchronization. The synchronization is oriented to thread access and resource control, it defines whether a thread can obtain resources and queue threads. Locks and synchronizers are well isolated from the fields that need to be concerned. Strictly speaking, synchronizers can be applied to synchronization facilities (including locks) other than locks ).

    2. The implementation of the CLH algorithm lock is based on the CLH algorithm. The following is a brief introduction to the CLH algorithm: The CLH algorithm creates an implicit linked list, which is an implementation of a non-blocking algorithm. The node QNode in the clh queue contains a locked field. If this field is set to true, the thread needs to obtain the lock without releasing the lock. If it is set to false, the thread releases the lock. Nodes are connected through an invisible linked list. This is called an invisible linked list because there is no obvious next pointer between these nodes. Instead, the behavior of myNode is affected by the change of the node pointed to by myPred. There is also a tail pointer on CLHLock, always pointing to the last node of the queue. The CLHLock class diagram is as follows: vcewx/examples + examples/e94bXjoaPI58/CzbzL + cq + examples/ijrMbkbXlOb2Rl0/examples + zzELSsrzTyOu1vc/examples + examples/examples = "http://www.2cto.com/uploadfile/Collfiles/20140213/2014021309011484.jpg" width =" 500 "height =" 400 "alt =" \ ">
    The code for the entire CLH is as follows, where the ThreadLocal class is used to bind the QNode to each thread and AtomicReference is used. The last pointer is modified by calling its getAndSet () it can update object references in an atomic way. The code for the CLH algorithm is as follows:
    Import java. util. concurrent. atomic. optional; public class CLHLock {public static class CLHNode {private boolean isLocked = true; // The default value is the waiting lock} @ SuppressWarnings ("unused") private volatile CLHNode tail; private static final AtomicReferenceFieldUpdater
       
        
    UPDATER = AtomicReferenceFieldUpdater. newUpdater (CLHLock. class, CLHNode. class, "tail"); public void lock (CLHNode currentThread) {CLHNode preNode = UPDATER. getAndSet (this, currentThread); if (preNode! = Null) {// The existing thread occupies the lock and enters the spin while (preNode. isLocked) {}} public void unlock (CLHNode currentThread) {// If the queue contains only the current thread, release the reference to the current thread (for GC ). If (! UPDATER. compareAndSet (this, currentThread, null) {// There are also follow-up threads currentThread. isLocked = false; // change the status and let the subsequent threads end the spin }}}
       
    As for the implementation of AQS, it is slightly different from CLH. As mentioned at the beginning of the synchronization engine, its implementation depends on a FIFO queue. The element Node in the queue is the container that stores thread reference and thread status, each thread's access to the SYN can be seen as a node in the queue. Node mainly includes the following member variables:
    Node {    int waitStatus;    Node prev;    Node next;    Node nextWaiter;    Thread thread;}

    The member variables are mainly responsible for saving the thread reference of the node, synchronizing the frontend and successor nodes of the waiting queue (hereinafter referred to as the sync Queue), and also including the synchronization status. The node is the basis for building the sync queue and the condition queue. The synchronization queue is included in the synchronization. The synx has three member variables: head of the sync queue header node, tail of the sync queue tail node, and state. For the lock acquisition, the request forms a node and mounts it to the end. The transfer of the lock Resource (Release and then obtain) starts from the header and proceeds backward. For the state maintained by the synchronizator, multiple threads obtain a chain structure.

    3. Implementation Analysis of AQS 3.1 Overview The design of the synchronization machine includes two operations: Getting and releasing:
    The procedure is as follows:
    If (successful retrieval attempt) {return;} else {added to the waiting queue; park yourself}

    Release Operation:
    If (attempt to release successfully) {unpark waiting for the first node in the queue} else {return false}

    To meet the preceding two operations, the following three points are required:
    1. atomic operation synchronization status;
    2. Block or wake up a thread;

    3. An internal queue should be maintained.

    The implementation of AQS adopts the template design mode. In the AbstractQueuedSynchronizer class

    protected boolean tryAcquire(int arg);protected int tryAcquireShared(int arg);protected boolean tryRelease(int arg);protected boolean tryReleaseShared(int arg);
    Sub-classes need to implement these methods to implement different Synchronization Methods.

    3.2 obtain and release locks 3.2.1 obtain and release locks

    The code for obtaining the lock operation is as follows:

    Public final void acquire (int arg) {if (! TryAcquire (arg) & acquireQueued (addWaiter (Node. EXCLUSIVE), arg) selfInterrupt (); // if there is an interruption in the process of obtaining the lock, the response is interrupted after the acquisition operation is complete. }

    The above logic mainly includes:
    1. Try to get it (call tryAcquire to change the State and ensure atomicity );
    In the tryAcquire method, the state operation method provided by the synchronizer is used. compareAndSet ensures that only one thread can successfully modify the state, threads that have not been modified will enter the sync queue Queue (by calling the addWaiter method)

    The addWaiter method is as follows:

    Private Node addWaiter (Node mode) {Node node = new Node (Thread. currentThread (), mode); // Try the fast path of enq; backup to full enq on failure first add quickly at the end, and then call the enq method Node pred = tail after failure; if (pred! = Null) {node. prev = pred; if (compareAndSetTail (pred, node) {// use the CAS operation to perform the pred. next = node; return node; }}enq (node); return node ;}

    2. If not, construct the current thread as a Node and add it to the sync queue;
    Each thread entering the queue is a Node, forming a two-way queue, similar to the CLH queue, in this way, the communication between threads is limited to a small scale (that is, about two nodes ).
    3. Try again (call the acquireQueued method). If not, remove the current thread from the thread scheduler and enter the waiting state.

    The acquireQueued code is as follows:

    Final boolean acquireQueued (final Node, int arg) {boolean failed = true; try {boolean interrupted = false; for (;) {final node p = Node. predecessor (); if (p = head & tryAcquire (arg) {// if it is a header node and the lock is obtained successfully, exit. Note: the head actually stores the nodes that have obtained the lock, namely the dumb node setHead (node); p. next = null; // help GC failed = false; return interrupted;} if (shouldParkAfterFailedAcquire (p, node) & parkAndCheckInterrupt () // parkAndCheckInterrupt will call the park method, enable the current thread to enter the waiting status interrupted = true ;}} finally {if (failed) cancelAcquire (node );}}
    The above logic mainly includes:
    1. Obtain the front node of the current node;
    You need to obtain the front node of the current node, and the corresponding meaning of the header node is that the current station has a lock and is running.
    2. When the front-end node is a head node and can get the status, it indicates that the current node occupies the lock;
    If the above conditions are met, the lock can be possessed. Based on the meaning of the lock on the node, set the header node to the current node.
    3. Otherwise, the system enters the waiting status.
    If it is not the turn of the current node to run, remove the current thread from the thread scheduler, that is, enter the waiting state.


    It should be noted that acquire does not respond to external interruptions in a timely manner during execution. After the execution is completed, acquire must respond to the interruption if it is interrupted. Similar to the acquire method, the acquireInterruptibly method provides the ability to get the status. Of course, if the status cannot be obtained, it will enter the sync queue for queuing. This is similar to acquire, however, unlike acquire, acquire can terminate the State retrieval operation before the current thread is interrupted. In other words, it is similar to synchronized to obtain the lock, the outside world can interrupt the current thread, and the operation to obtain the lock can respond to the interruption and return in advance. When a thread is in a synchronized block or performing synchronous I/O operations, the thread is interrupted. At this time, the thread's interrupt flag is set to true, but the thread continues to run.

    3.2.2 The release operation code is as follows:
    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 above logic mainly includes:
    1. Try to release the status;
    TryRelease can ensure that the state is set back to atomicity. Of course, compareAndSet must be used. If the release status is successful, it will start the wake-up process of the successor node.
    2. Wake up the threads contained by the successor node of the current node.
    The LockSupport unpark method is used to wake up a sleeping thread and let it continue to the acquire state.








Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.