Analysis of Java Concurrency Aqs principle (I.)

Source: Internet
Author: User
Tags cas prev volatile

The Aqs we're talking about is AbstractQueuedSynchronizer that he's java.util.concurrent.locks under the package, this class is a core class of Java concurrency. The first time you know that this class is in the view of a reentrant lock ReentrantLock , in which ReentrantLock an inner class Sync inherits from AbstractQueuedSynchronizer , is ReentrantLock the core implementation. The locks in the bundle are almost all based on Aqs, but when you look at the source code, they are not inherited directly AbstractQueuedSynchronizer , but are implemented through internal classes Sync .

abstract static class Sync extends AbstractQueuedSynchronizer

The note here is AbstractQueuedSynchronizer an abstract class that defines the basic framework. The AQS core uses a variable state to represent the state.
Aqs that is, AbstractQueuedSynchronizer this class just defines a queue management thread, for the state of the thread is a subclass of maintenance, we can understand as the division of a synchronization queue, when the thread acquires a lock failure (multi-threaded contention resources are blocked when the queue will be entered), threads will be added to the queue tail

Summarize:

    • AQS is only responsible for managing thread blocking queues.
    • Blocking and waking of threads

The Synchronizer is the key to implementing the lock (for example, the Aqs queue Synchronizer) and uses the Synchronizer to implement the definition of the lock. The key is user-oriented, and it defines the interface that the consumer and the lock interact with, but hides the implementation details. Synchronizer is the implementation of the lock, so he is in the back of the lock to make contributions, the user can not directly contact him, he simplified the implementation of the lock, blocking the synchronization state management, queuing between threads, wait, wake up and other operations. This design is a good way to isolate the areas of concern for users and the stakeholders.

The above represents the pattern of the queue, which represents the head node of the queue head , which tail represents the tail node of the queue. In the source code their definitions volatile are used defined. The use volatile of keywords guarantees the visibility of variables in memory, see: volatile keyword parsing. Ensure that a thread is seen by other threads while it is in the queue.

private transient volatile Node head;//头节点private transient volatile Node tail;//尾节点

There is AbstractQueuedSynchronizer also an inner class in this class Node that is used to build the node class of the queue element.

Two types of resource sharing are defined in Aqs:

    • Exclusive: Exclusive Type
    • Share: Shared

      When acquired in exclusive mode, attempts to get through other threads cannot succeed. The shared mode acquired by multithreading may (but is not required) succeed. When the shared mode gets successful, the next waiting thread (if it exists) must also determine whether it is also available. Threads that are waiting in different modes share the same FIFO queue.

In different implementation classes, in order to implement different functions, different sharing methods are used, for example, a reentrant lock ReentrantLock is an exclusive lock.
The different implementation classes of the Aqs do not need to focus on the maintenance and management of the thread waiting queue (thread blocking queued, wake out of the team), in Aqs these are already defined, the different Synchronizer only need to implement the following methods:

//独占方式尝试获取资源protected boolean tryAcquire(int arg)//独占方式尝试释放资源protected boolean tryRelease(int arg)//共享方式尝试获取资源,返回值0表示成功但是没有剩余资源,负数表示失败,正数表示成功且有剩余资源protected int tryAcquireShared(int arg)//共享方式尝试释放资源protected boolean tryReleaseShared(int arg)

All custom synchronizers need only to determine how they are contributing to the resource: shared, exclusive. You can also implement both shared and exclusive read and ReentrantReadWriteLock write locks, and multiple threads can read at the same time, but only one thread writes.

Exclusive mode synchronization state gets:

First look at the place where the code begins to execute:

Gets the resource in exclusive mode, ignoring the interrupt. (If you get to a resource, return the result directly, otherwise go to the wait queue and wait for the resource to be fetched again.) ) is returned successfully by invoking at least one tryAcquire(int) implementation. Otherwise, threads are queued and may be repeatedly blocked and unblocked until they are successfully invoked tryAcquire(int) .

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

The order in which the method executes:

    • Invoke tryAcquire() method attempts to fetch resources, implemented in subclasses
    • The calling addWaiter() method marks the current thread as exclusive and joins the tail of the queue

Here's addWaiter() the first parameter in the method, the thread waits for the elements in the queue to be Node stored using this inner class, Node with two member variables declaring how the resource is shared:

        static final Node SHARED = new Node();//共享式        static final Node EXCLUSIVE = null;//独占式
    • Call the acquireQueued() method, let the thread wait for the resource in the queue, get the resource, return it if it is interrupted during the wait, return true, otherwise false

The method is called first in the method tryAcquire(int) , which AbstractQueuedSynchronizer is not implemented and requires subclasses to implement:

    protected boolean tryAcquire(int arg) {        throw new UnsupportedOperationException();    }

Second Step call addWaiter() method: This method is responsible for maintaining the thread waiting queue method, so in the AbstractQueuedSynchronizer implementation of the method: specifically, a node class is created, put the node at the end of the team, if the failure of the call enq(node) method (tail node is empty).

Addwaiter () Method:

private Node addWaiter(Node mode) {        Node node = new Node(Thread.currentThread(), mode);        // Try the fast path of enq; backup to full enq on failure        Node pred = tail;        if (pred != null) {            node.prev = pred;            if (compareAndSetTail(pred, node)) {                pred.next = node;                return node;            }        }        enq(node);        return node;    }

The above method is judged if added to the end of the queue failed
Enq () Method:

private Node enq(final Node node) {        for (;;) {            Node t = tail;            //如果队列为空(队尾元素为空)创建节点添加进去            if (t == null) { // Must initialize                if (compareAndSetHead(new Node()))                    //把tail指向head                    tail = head;            } else {                //正常添加到队尾                node.prev = t;                if (compareAndSetTail(t, node)) {                    t.next = node;                    return t;                }            }        }    }

In the above code to add nodes are used to compare and Exchange (CAS, can be said to be a solution in the concurrency environment), the compareAndSetTail() method can ensure that the node can be safely added to the queue, in a multithreaded environment can not guarantee that an element is correctly added to the tail of the queue. Since the elements entering the queue are placed at the end of the line, in order to ensure the correctness of the data, the use of CASwhen the tail node is set.
The third step is to invoke the acquireQueued() method in order to wait in the queue for the resource to be woken up, because the previous operation failed, the thread is put into the tail, the queue is the first-out structure, so the thread at the tail must wait for it to wake up. There is a dead loop in the method, we call it spin, and only when the condition is satisfied, get the synchronous state and exit the spin.
Acquirequeued () Method:

  Final Boolean acquirequeued (final node node, int arg) {//Set Success Token Boolean failed = true;            try {//Set interrupt token Boolean interrupted = false; for (;;)                {//Get node's precursor to final nodes P = node.predecessor ();                    Determine if the precursor node is a header if (p = = head && tryacquire (ARG)) {//node is set as the head junction.                    Sethead (node); Set the precursor of the P node to null, see explanation below p.next = null;                    Help GC failed = false;                return interrupted; }//Determine whether to continue waiting for if (Shouldparkafterfailedacquire (p, node) && Parkand            Checkinterrupt ()) interrupted = true;        }} finally {if (failed) cancelacquire (node); }    }

Set the front of the p node to null, that is, the previous head node, in the above source code in the comments marked as Help GC function, explain: in the call above the Sethead () method, the method's internal has already set the current node's predecessor nodes to NULL, Here again, to ensure that the current node's predecessor node is successfully recycled (the current node is set to the head node, then the previous head nodes will be released to simulate a normal outbound process). Draw a better understanding of yourself.

Sethead () Method:

    private void setHead(Node node) {        head = node;        node.thread = null;        node.prev = null;    }

Here we analyze the method called above acquireQueued()

private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {        //获取前驱节点的状态        int ws = pred.waitStatus;        //如果当前节点状态值为SIGNAL这个值,代表当前线程应该被挂起,等待被唤醒        if (ws == Node.SIGNAL)            return true;        if (ws > 0) {            //如果大于0代表将当前节点的前驱节点移除            do {                node.prev = pred = pred.prev;            } while (pred.waitStatus > 0);            pred.next = node;        } else {            //小于0时把前驱结点状态值设置为SIGNAL,目的是为了前驱判断后将当前节点挂起(通知自己一下)            compareAndSetWaitStatus(pred, ws, Node.SIGNAL);        }        return false;    }

Here we need to look at the definition Node of the state value defined in this class:

        //表示线程已取消,作废状态        static final int CANCELLED =  1;        //表示后继节点应该等待当前节点释放资源后唤醒其后继节点        static final int SIGNAL    = -1;        //表示当前正处于等待状态        static final int CONDITION = -2;        //表示状态需要向后传播        static final int PROPAGATE = -3;
    • CANCELLED Cancel Status
    • SIGNAL wait for Trigger status
    • CONDITION Wait Condition status
    • PROPAGATE state needs to be propagated backwards

The wait queue is FIFO first-out, and the current node's thread can be suspended only if the previous node's state is signal. so when the method is called, the predecessor node is set to signal.
Because the previous node is set to signal, the thread needs to be executed, but it does not follow the threads behind it, and the subsequent thread must find a node that is not the cancel of the predecessor node, then sets it to signal and then hangs in place, waiting to wake up. Because the signal execution is done, it wakes up the next one immediately after.

Summarize:
A template method, defined in Aqs, that acquire() attempts to fetch a resource by invoking a method in the subclass, tryAcquire() succeeds, returns, and the failed call addWaiter() adds the current thread to the end of the blocking queue and marks it as exclusive. acquireQueued()method to get the synchronization state by spin (this method causes the thread to wait in the queue for rest, and when there is an opportunity to try to get the resource), the node tries to get the resource if the predecessor node of the current node is the head node, tries to acquire the resource before returning, and if there is an interruption and does not respond during the whole waiting process Call method settings interrupt after acquiring a resource selfInterrupt() .

Release of the synchronization state in exclusive mode:

The above based on the source analysis of the exclusive mode to obtain the lock process is mainly called the template method acquire() method downward analysis, then we analyze its opposite method, the exclusive mode of the release of the lock process, or a template methodrelease()

public final boolean release(int arg) {        if (tryRelease(arg)) {            //得到头节点            Node h = head;            //判断头节点不为空,状态值符合条件            if (h != null && h.waitStatus != 0)                //唤醒下一个等待线程                unparkSuccessor(h);            return true;        }        return false;    }

tryRelease()Methods still need subclasses to implement themselves

protected boolean tryRelease(int arg) {        throw new UnsupportedOperationException();    }

unparkSuccessor()Method:

private void unparkSuccessor(Node node) {        //获得当前线程的状态值        int ws = node.waitStatus;        if (ws < 0)            //小于0时置零            compareAndSetWaitStatus(node, ws, 0);        //获得当前节点的后继节点        Node s = node.next;        //判断为空和状态值是否大于0        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);    }

unpark()Method wakes up to the front-most thread in the queue, and then executes the above procedure again.

Summary: When acquiring a synchronization, the Synchronizer maintains a synchronization queue at the user's point of view, and the thread that gets the failed state is added to the queue and spins, and the queue is moved out when the node's precursor node is the head node and the synchronization state is obtained. When released, the call tryRelease() frees and wakes the successor node.

Analysis of Java Concurrency Aqs principle (I.)

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.