AQS-depth understanding of Java

AQS

AQS Overview

AbstractQueuedSynchronizer abstract queue synchronizer referred AQS, which is the basis of component implementation synchronizer, juc below Lock and some concurrent implementation tools is achieved through AQS, through the class diagram here we look at probably the AQS, we summarize below AQS about the realization of the principle. Take a look at the class diagram of AQS.

(1) AQS is a built-in FIFO to complete the two-way queuing thread work (through internal node recording head and the tail end of the first team and the team element, the element node types Node type, later we will see Node the specific configuration).

/*等待队列的队首结点(懒加载,这里体现为竞争失败的情况下,加入同步队列的线程执行到enq方法的时候会创
建一个Head结点)。该结点只能被setHead方法修改。并且结点的waitStatus不能为CANCELLED*/
private transient volatile Node head;
/**等待队列的尾节点,也是懒加载的。(enq方法)。只在加入新的阻塞结点的情况下修改*/
private transient volatile Node tail;
复制代码

(2) wherein the Node in the thread queue for storing threads into the AQS reference node Node inside thread SHARED flag is represented as a shared resource acquisition failure is added to the blocked queue; Node thread as represented in EXCLUSIVE failure to acquire exclusive resources are blocked added to the queue. waitStatus represents the state of the current thread waiting:

①CANCELLED = 1: indicates that the thread because the interrupt or wait for a timeout, you need to cancel the wait from the waiting queue;

②SIGNAL = -1: thread1 current thread lock occupied, queue head (merely representative of the first node, which is not stored thread reference) node1 successor node in a wait state, if the thread has possession of the lock or lock release thread1 CANCEL and then we will notify the node node1 to acquire the lock execution.

③CONDITION = -2: represents the nodes in the waiting queue (in this case is waiting on a lock condition, the following principles will be written on Condition), when the thread holding the lock calls Condition of signal () after the method, the node will transfer from the wait queue to the condition of the lock of the synchronous queue, to compete lock. (Note: This synchronization queue is what we mean AQS maintenance FIFO queue, waiting queue is a queue associated with each condition)

④PROPAGTE = -3: obtaining a shared state will be transferred to the successor node acquires the shared state represents the synchronization.

** (3) ** AQS maintained in a single state information that volatile state (by the AQS Unsafe related method, in order to obtain the atomic this manner by the thread state). AQS provides getState (), setState (), compareAndSetState () function modifies the value (actually calls compareAndSwapInt method of unsafe). Here are some of the member variable AQS and the renewal of state

//这就是我们刚刚说到的head结点,懒加载的(只有竞争失败需要构建同步队列的时候,才会创建这个head),如果头节点存在,它的waitStatus不能为CANCELLED
private transient volatile Node head;
//当前同步队列尾节点的引用,也是懒加载的,只有调用enq方法的时候会添加一个新的wait node
private transient volatile Node tail;
//AQS核心:同步状态
private volatile int state;
protected final int getState() {
    return state;
}
protected final void setState(int newState) {
    state = newState;
}
protected final boolean compareAndSetState(int expect, int update) {
    return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}
复制代码

(4) AQS-based designer template method pattern. When using synchronous need to inherit and override the specified method, and the subclass generally recommended as defined synchronization component static inner classes, subclasses override these methods later used for AQS work is a template method provided in these templates subclass override the calling method. Wherein subclasses can override the method:

//独占式的获取同步状态,实现该方法需要查询当前状态并判断同步状态是否符合预期,然后再进行CAS设置同步状态
protected boolean tryAcquire(int arg) {	throw new UnsupportedOperationException();}
//独占式的释放同步状态,等待获取同步状态的线程可以有机会获取同步状态
protected boolean tryRelease(int arg) {	throw new UnsupportedOperationException();}
//共享式的获取同步状态
protected int tryAcquireShared(int arg) { throw new UnsupportedOperationException();}
//尝试将状态设置为以共享模式释放同步状态。 该方法总是由执行释放的线程调用。 
protected int tryReleaseShared(int arg) { throw new UnsupportedOperationException(); }
//当前同步器是否在独占模式下被线程占用,一般该方法表示是否被当前线程所独占
protected int isHeldExclusively(int arg) {	throw new UnsupportedOperationException();}
复制代码

(. 5) AQS inner class ConditionObject is achieved by binding thread synchronization locking, AQS ConditionObject access variables (state, queue) directly, ConditionObject a condition variable corresponding to each ConditionObject await a call queue for storing threads have a condition variable after the method is blocked thread.

AQS in exclusive mode

Above we simply understand a bit AQS basic components, here by ReentrantLock of unfair lock to lock and release the lock a specific analysis of the process of AQS exclusive mode of implementation.

Unfair lock locking process

Simply put, AQS all requests will constitute a thread CLH queue, when a thread is finished (lock.unlock ()) will activate its successor node, but not the executing thread in the queue, and those who wait all thread of execution is blocked (park ()). As shown below.

** (1) ** Assumes this time in the initial case, not how competitive this task to request state, at this time if the first thread thread1 calls the lock method to request a lock, will first way state by CAS updated to 1, he said he received a thread1 lock and an exclusive lock thread holder to thread1.

final void lock() {
    if (compareAndSetState(0, 1))
        //setExclusiveOwnerThread是AbstractOwnableSynchronizer的方法,AQS继承了AbstractOwnableSynchronizer
        setExclusiveOwnerThread(Thread.currentThread());
    else
        acquire(1);
}
复制代码

** (2) ** this time to try to have another thread thread2 or lock, also call lock method, try to CAS by way of the state is updated to 1, but because there is already before the state holds a thread, so thread2 this step CAS failure (front thread1 has obtained state and not released), it will call acquire (1) method (which is provided by AQS template method, it will call tryAcquire for subclasses). Achieve unfair lock in, AQS template method acquire (1) method is called tryAcquire NofairSync, and Sync method of nonfairTryAcquire tryAcquire turn calls, so we take a look at nonfairTryAcquire process.

//NofairSync
protected final boolean tryAcquire(int acquires) {
    return nonfairTryAcquire(acquires);
}
final boolean nonfairTryAcquire(int acquires) {
    //(1)获取当前线程
    final Thread current = Thread.currentThread();
    //(2)获得当前同步状态state
    int c = getState();
    //(3)如果state==0,表示没有线程获取
    if (c == 0) {
        //(3-1)那么就尝试以CAS的方式更新state的值
        if (compareAndSetState(0, acquires)) {
            //(3-2)如果更新成功,就设置当前独占模式下同步状态的持有者为当前线程
            setExclusiveOwnerThread(current);
            //(3-3)获得成功之后,返回true
            return true;
        }
    }
    //(4)这里是重入锁的逻辑
    else if (current == getExclusiveOwnerThread()) {
        //(4-1)判断当前占有state的线程就是当前来再次获取state的线程之后,就计算重入后的state
        int nextc = c + acquires;
        //(4-2)这里是风险处理
        if (nextc < 0) // overflow
            throw new Error("Maximum lock count exceeded");
        //(4-3)通过setState无条件的设置state的值,(因为这里也只有一个线程操作state的值,即
        //已经获取到的线程,所以没有进行CAS操作)
        setState(nextc);
        return true;
    }
    //(5)没有获得state,也不是重入,就返回false
    return false;
}
复制代码

Conclusion is:

1, to obtain the current thread thread2 going to acquire a lock.

2, get the value of the current state of AQS. If the state at this time the value is 0, then we get a lock operation by CAS, and then set the AQS thread occupants to thread2. Clearly, in the current implementation of this, the value of state is 1 not 0, because our thread1 not release the lock. Therefore CAS failed, the logic behind the re-entry of step 3 is not performed

3, if the current thread to acquire the lock going at this time is equal to the AQS exclusiveOwnerThread thread, the state at this time is added to the value 1, which is locked reentrant implementations.

4, eventually thread2 executed here will return false.

* (3) * above thread2 lock fails, it returns false. So according to AQS overview we talked about the beginning of construction should be thread2 Node node added to a synchronization queue. Because NofairSync of tryAcquire by the template method is a method of AQS acquire () is invoked, then we look at the source as well as the method of execution flow.

//(1)tryAcquire,这里thread2执行返回了false,那么就会执行addWaiter将当前线程构造为一个结点加入同步队列中
public final void acquire(int arg) {
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}
复制代码

Then we look at the implementation process addWaiter methods.

private Node addWaiter(Node mode) {
    //(1)将当前线程以及阻塞原因(是因为SHARED模式获取state失败还是EXCLUSIVE获取失败)构造为Node结点
    Node node = new Node(Thread.currentThread(), mode);
    //(2)这一步是快速将当前线程插入队列尾部
    Node pred = tail;
    if (pred != null) {
        //(2-1)将构造后的node结点的前驱结点设置为tail
        node.prev = pred;
        //(2-2)以CAS的方式设置当前的node结点为tail结点
        if (compareAndSetTail(pred, node)) {
            //(2-3)CAS设置成功,就将原来的tail的next结点设置为当前的node结点。这样这个双向队
            //列就更新完成了
            pred.next = node;
            return node;
        }
    }
    //(3)执行到这里,说明要么当前队列为null,要么存在多个线程竞争失败都去将自己设置为tail结点,
    //那么就会有线程在上面(2-2)的CAS设置中失败,就会到这里调用enq方法
    enq(node);
    return node;
}
复制代码

So to summarize the method add Waiter

1, the current thread to acquire the lock going i.e. thread2 exclusive mode and packaged as a node object.

2, attempts to quickly add the current node configured as a node thread tail nodes (this is direct access to the current tail, then the node's predecessor nodes to tail), and in a manner CAS tail node to node point (CAS after the success of the original tail is set next node, and then update the queue successful).

3, provided if 2 fails, the method proceeds to enq.

In thread1 and thread2 environment just start when the thread is blocked queue is empty (because thread1 acquired lock, thread2 is just to request a lock, so the thread is blocked inside the queue is empty). Obviously, this time trailing tail of the queue node is null, then it will go directly to the enq method. So we look at the implementation of the method enq

private Node enq(final Node node) {
    for (;;) {
        //(4)还是先获取当前队列的tail结点
        Node t = tail;
        //(5)如果tail为null,表示当前同步队列为null,就必须初始化这个同步队列的head和tail(建
        //立一个哨兵结点)
        if (t == null) { 
            //(5-1)初始情况下,多个线程竞争失败,在检查的时候都发现没有哨兵结点,所以需要CAS的
            //设置哨兵结点
            if (compareAndSetHead(new Node()))
                tail = head;
        } 
        //(6)tail不为null
        else {
            //(6-1)直接将当前结点的前驱结点设置为tail结点
            node.prev = t;
            //(6-2)前驱结点设置完毕之后,还需要以CAS的方式将自己设置为tail结点,如果设置失败,
            //就会重新进入循环判断一遍
            if (compareAndSetTail(t, node)) {
                t.next = node;
                return t;
            }
        }
    }
}
复制代码

Internal enq method is a spin cycle, the first cycle by default as shown below

1, the first block of code (4) of the tail point t, it is determined to give t == null, as shown in (1);

2, so the need to create a sentinel node as the primary node entire synchronization queue (performed at block 5-1)

3, after finished as shown in (2). In this way the first cycle is finished.

The entire second cycle executed as shown below.

1, or the first tail current node and acquires the tail junction point t. As shown in (3)

2, then this is determined to give t! = Null, so enq method enters block (6).

3, at (6-1) in the precursor block node junction to the original queue tail node, as shown below in (4).

4, after completing predecessor node set, code block (6-2) will CAS manner the current node is set to node tail node, if the setting is successful, the next diagram (5). After completion of the update tail node, queues need to ensure a two-way, so the original point t sentinel node points to the next node node node, as shown below (6). Finally return.

In conclusion, even in the case of multi-threaded, enq method was able to ensure that each thread node will be added to the security of synchronous queue because enq add nodes by CAS will not return to the way synchronous queue, otherwise will continue to try to add (this is actually in the concurrent case, to add to the queue Node synchronization becomes serialized)

** (4) ** template in the above method of AQS, Acquire () method step also acquireQueued, the main effect of this method is that sniffing their precursors in the synchronization queue node, if the node is the head of the precursor node, it would try to take to obtain synchronization status, otherwise it will first set up your own waitStatus to -1, and then call the method LockSupport park itself. As shown in the following implementation specific code

final boolean acquireQueued(final Node node, int arg) {
    boolean failed = true;
    try {
        boolean interrupted = false;
        //在这样一个循环中尝试tryAcquire同步状态
        for (;;) {
            //获取前驱结点
            final Node p = node.predecessor();
            //(1)如果前驱结点是头节点,就尝试取获取同步状态,这里的tryAcquire方法相当于还是调
            //用NofairSync的tryAcquire方法,在上面已经说过
            if (p == head && tryAcquire(arg)) {
                //如果前驱结点是头节点并且tryAcquire返回true,那么就重新设置头节点为node
                setHead(node);
                p.next = null; //将原来的头节点的next设置为null,交由GC去回收它
                failed = false;
                return interrupted;
            }
            //(2)如果不是头节点,或者虽然前驱结点是头节点但是尝试获取同步状态失败就会将node结点
            //的waitStatus设置为-1(SIGNAL),并且park自己,等待前驱结点的唤醒。至于唤醒的细节
            //下面会说到
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}
复制代码

In the above code, we can see that this method is also a spin cycle, according to just continue to analyze the situation of thread1 and thread2. After completion of the implementation of the method enq, queue synchronization is probably as follows.

Predecessor node the current node is node head, so calls to tryAcquire () method to get synchronized. However, because state is thread1 possession, so tryAcquire failure. AcquireQueued block of code, this is a method (2) of the. Code block (2) in the first call shouldParkAfterFailedAcquire method, waitStatus predecessor node in the node synchronization method would node queue thread removal CANCELLED, and the current call belongs to the thread and his own node waitStatus predecessor node is set to -1 (SIGNAL), and then returns. The specific method implementation is shown below.

private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
    //(1)获取前驱结点的waitStatus
    int ws = pred.waitStatus;
    //(2)如果前驱结点的waitStatus为SINGNAL,就直接返回true
    if (ws == Node.SIGNAL)
        //前驱结点的状态为SIGNAL,那么该结点就能够安全的调用park方法阻塞自己了。
        return true;
    if (ws > 0) {
        //(3)这里就是将所有的前驱结点状态为CANCELLED的都移除
        do {
            node.prev = pred = pred.prev;
        } while (pred.waitStatus > 0);
        pred.next = node;
    } else {
        //CAS操作将这个前驱节点设置成SIGHNAL。
        compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
    }
    return false;
}
复制代码

Therefore shouldParkAfterFailedAcquire method is finished, the current synchronous queue like this is probably the case that the sentinel node waitStatus value becomes -1.

The above is finished, when the method returns to acquireQueued in acquireQueued process will be the second cycle, but still failed acquisition state, while the time when the method again enters shouldParkAfterFailedAcquire current junction node of the head node of the precursor waitStatus already -1 (SIGNAL), it will return true, then the method will then perform acquireQueued parkAndCheckInterrupt blocked pending their park.

private final boolean parkAndCheckInterrupt() {
    LockSupport.park(this);
    return Thread.interrupted();
}
复制代码

** (5) ** We comb the entire process method call, assuming that there is now a thread3 thread competition this state, the flow of this method call is what does.

① of course, is calling ** ReentrantLock.lock () ** method to try to lock;

② Because the non-lock fair, so it will go to the call ** NoFairSync.lock () ** method;

③ in NoFairSync.lock () method, the first attempt to set the value of the state, as it has been occupied so sure is a failure. This time will call the template method of AQS AQS.acquire (1) .

Template Method ④ in the AQS acquire (1), the actual first calls is tryAcquire () method in the subclass, and in the realization of that is unfair lock ** Sync.nofairTryAcquire () ** method.

⑤ apparently tryAcquire () returns false, so acquire () to continue, that call AQS.addWaiter () , the current thread will construct called a Node node, waitStatus the initial condition is 0.

⑥ in addWaiter method, will first try to directly build the node to node CAS way (there are multiple threads try to set up their own tail) is set to tail node, if set successfully direct return, it would fail the process of entering a spin cycle. That call ** enq () ** method. Ultimately ensure their success is added to the synchronization queue.

⑦ After the addition of the synchronous queue, it is necessary to suspend themselves or their precursors sniffing whether the node is the first node to attempt to acquire synchronization state. That call ** acquireQueued () ** method.

Here predecessor node ⑧ not thread3 head node, so a direct call ** shouldParkAfterFailedAcquire () ** method, waitStatue value which will just the first thread thread2 node is changed to 1 (the initial when there is no change in the waitStatus, each new node is added to the value of the precursor will change waitStatus node).

After waitStatus ⑨thread2 where node changes, shouldParkAfterFailedAcquire method returns false. So then there will be a second cycle in acquireQueued in. ShouldParkAfterFailedAcquire method and call again, and then returns true. The final call ** parkAndCheckInterrupt () ** will hang himself.

Each thread synchronization to compete in this state if it fails it will probably go through these processes above. Suppose now thread3 experience these processes also entered after the above synchronization queue, then the entire queue is probably below the synchronous case.

The above process is probably sort out the following chart

Unfair lock release procedure

It says a ReentrantLock an example of how to get to the unfair lock, then thread1 acquire the lock, the lock is released executing the process like it. Of course, it is to call ReentrantLock.unlock () method is finally in, so we began to read from this method.

** (1) ** from the following unlock methods we can see, is actually called release AQS () method, wherein the transmission parameter is 1, each showing a call release unlock methods are obtained state. Calls unlock method multiple times in the case of re-entry, but also to ensure the lock and unlock are paired.

public void unlock() {
    sync.release(1); //这里ReentrantLock的unlock方法调用了AQS的release方法
}
public final boolean release(int arg) {
	//这里调用了子类的tryRelease方法,即ReentrantLock的内部类Sync的tryRelease方法
    if (tryRelease(arg)) {
        Node h = head;
        if (h != null && h.waitStatus != 0)
            unparkSuccessor(h);
        return true;
    }
    return false;
}
复制代码

** (2) ** see above release method first calls ReentrantLock inside the tryRelease class Sync method. And by analyzing the following code probably know tryRelease do these things.

① get the current state AQS, minus 1;

② determine whether the current thread is equal exclusiveOwnerThread AQS, if not, throw an exception IllegalMonitorStateException, which ensures locking and releasing locks a thread must be the same;

③ If the (state-1) is the result is not 0, the described lock is reentrant, unlock multiple times, which is the reason for the pair of lock and unlock;

④ If the (state-1) equals 0, we will set the AQS ExclusiveOwnerThread null;

⑤ If this operation is successful, that is tryRelase method returns true; return false representation requires multiple unlock.

protected final boolean tryRelease(int releases) {
    //(1)获取当前的state,然后减1,得到要更新的state
    int c = getState() - releases;
    //(2)判断当前调用的线程是不是持有锁的线程,如果不是抛出IllegalMonitorStateException
    if (Thread.currentThread() != getExclusiveOwnerThread())
        throw new IllegalMonitorStateException();
    boolean free = false;
    //(3)判断更新后的state是不是0
    if (c == 0) {
        free = true;
        //(3-1)将当前锁持者设为null
        setExclusiveOwnerThread(null);
    }
    //(4)设置当前state=c=getState()-releases
    setState(c);
    //(5)只有state==0,才会返回true
    return free;
}
复制代码

* (3) ** After tryRelease then returns true, the method will be executed if the content release statement block. From the above we can see,

if (tryRelease(arg)) {
    //(1)获取当前队列的头节点head
    Node h = head;
    //(2)判断头节点不为null,并且头结点的waitStatus不为0(CACCELLED)
    if (h != null && h.waitStatus != 0)
        //(3-1)调用下面的方法唤醒同步队列head结点的后继结点中的线程
        unparkSuccessor(h);
    return true;
}
复制代码

** (4) in the process analysis ** acquire the lock, we know that the current synchronous queue as shown below, it is determined to give head! = Null head and the waitStatus = -1. Parameters it will perform unparkSuccessor method, passing a reference point for the head of h. Then let's look at unparkSuccessor method dealt with anything.

private void unparkSuccessor(Node node) {
    //(1)获得node的waitStatus
    int ws = node.waitStatus;
    //(2)判断waitStatus是否小于0
    if (ws < 0)
        //(2-1)如果waitStatus小于0需要将其以CAS的方式设置为0
        compareAndSetWaitStatus(node, ws, 0);

    //(2)获得s的后继结点,这里即head的后继结点
    Node s = node.next;
    //(3)判断后继结点是否已经被移除,或者其waitStatus==CANCELLED
    if (s == null || s.waitStatus > 0) {
        //(3-1)如果s!=null,但是其waitStatus=CANCELLED需要将其设置为null
        s = null;
        //(3-2)会从尾部结点开始寻找,找到离head最近的不为null并且node.waitStatus的结点
        for (Node t = tail; t != null && t != node; t = t.prev)
            if (t.waitStatus <= 0)
                s = t;
    }
    //(4)node.next!=null或者找到的一个离head最近的结点不为null
    if (s != null)
        //(4-1)唤醒这个结点中的线程
        LockSupport.unpark(s.thread);
}
复制代码

Code implementation can be concluded from the above, unparkSuccessor mainly do two things:

① acquired head node waitStatus, is less than 0, by the CAS operation waitStatus head node is modified to 0

② find another node of the head node, if waitStatus this node is less than 0, it wakes up this node, or traverse down, find the first waitStatus <= 0 node, and wake up.

** (5) ** Now we should analyze is freed after state, then wake up in the queue node synchronization procedure is how to perform. Synchronous queue according to the above diagram, it executes the following

after thread1 (to get the lock thread) calls the unlock method, the method will eventually perform to unparkSuccessor wake thread2 nodes. So thread2 is to unpark .

Recall, when thread2 in parkAndCheckInterrupt after calling acquireQueued method which is blocked pending the park, so after thread2 wakes continue acquireQueued methods for loop (here can look forward memories acquireQueued methods What do something for loop);

for the cycle is the first thing to do in view of their predecessor node is not the first node (in the case of the above synchronous queue is satisfied);

predecessor node is the head node, it will call tryAcquire method attempts to acquire a state , because thread1 been released state, namely state = 0, so thread2 call tryAcquire method when it comes to CAS way to update the state from 0 to 1 to be successful , so this time thread2 on access to the lock

Thread2 get state is successful, it will withdraw from acquireQueued method. Note that this time acquireQueued return value is false, it will withdraw from if conditions acquire the template method of AQS, the last execution of program code blocks themselves locked in.

Guess you like

Origin juejin.im/post/5d40e5f26fb9a06b317b3e0d