[Concurrent the Java] the AQS parse source code synchronization queue abstraction - exclusive lock release procedure

[ The Java concurrent] the AQS parse source code synchronization queue abstraction - exclusive lock acquisition process

Previous 've explained the process of obtaining AQS exclusive lock, the next step is to release an exclusive lock AQS process of detailed analysis shows, ado, directly into the text ...

An inlet lock release release (int arg)

Under First explained, to be able to perform the normal way to release the thread here is to get the lock, the code can be seen from the following steps to release the lock only two important ways: tryRelease and unparkSuccessor , tryRelease try to release the lock, unparkSuccessor wake successor node the encapsulated thread.

public final boolean release(int arg) {
    // 尝试释放锁
    if (tryRelease(arg)) {
        Node h = head;
        // 如果头节点不为空,并且waitStatus不为0则唤醒后继节点
        if (h != null && h.waitStatus != 0)
            unparkSuccessor(h);
        // 无论是否执行唤醒后继节点,总会返回true
        return true;
    }
    // 释放失败
    return false;
}

Then we begin to analyze tryRelease and unparkSuccessor two main ways.

Try to release the lock tryRelease (int arg)

tryRelease method AQS does not implement specific default logic, as follows:

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

So, we took tryRelease specific implementation ReentrantLock be described,

protected final boolean tryRelease(int releases) {
    // 释放后的锁的计数(可重入锁)
    int c = getState() - releases;
    // 当前释放锁的必须为锁持有的线程
    if (Thread.currentThread() != getExclusiveOwnerThread())
        throw new IllegalMonitorStateException();
    boolean free = false;
    // 当锁计数为0时说明已锁已完全释放,将AQS中占有线程设为空
    if (c == 0) {
        free = true;
        setExclusiveOwnerThread(null);
    }
    setState(c);
    return free;
}

Wake successor node unparkSuccessor

 private void unparkSuccessor(Node node) {// 唤醒后继节点
     int ws = node.waitStatus;
     // waitStatus,直接将waitStatus设为0
     if (ws < 0)
         compareAndSetWaitStatus(node, ws, 0);
     Node s = node.next;
     // waitStatus > 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);
 }

A method of performing a logic unparkSuccessor:

1. If the head node waitStatus <0, it is set to 0 directly waitStatus

2. From the forward traversal to find waitStatus <= 0 nodes, and is the closest node to the first node, a successor node is the head node

3. find a successor node to be awakened wake of the node corresponding thread.

These are the main contents of this section talk about the next time will .....

Etc., etc...

That the release process AQS exclusive lock on this end of it? Not so simple.

Looking forward to re-release the code under lock, I do not know if I have found the problem?

problem

Why not method CAS 1.tryRelease carried out to reduce the lock count

2.unparkSuccessor method to determine why only the head node waitStatus <0, the waitStatus set to 0, then waitStatus> 0 how not to judge

In 3.unparkSuccessor if (s == null || s.waitStatus> 0) {...}, why determination waitStatus> 0

4. Why do we need to move forward after traversing thread wakes up to find the nearest and waitStatus <= successor node of the head node 0, can not traverse it from front to back?

Next, we explain one by one of the above questions.

Issue 1 tryRelease method Why not conduct CAS reduce lock count

In fact, this problem is a simple question, is also mentioned earlier, it is possible to release execution threads here are ways to get into the lock has a thread, and an exclusive lock can only be one thread, so no CAS Compare before assignment.

Issue 2 unparkSuccessor methods to determine why only the head node waitStatus <0, the waitStatus set to 0, then waitStatus> 0 how not to judge

Do you still remember an on content analysis, revisit shouldParkAfterFailedAcquire method, the current drive node waitStatus> 0, we will traverse weed out waitStatus> 0 node, therefore, the current head node must not be greater than waitStatus 0

private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
    int ws = pred.waitStatus;
    // 如果ws == Node.SIGNAL,则说明当前线程已经准备好被唤醒,因此现在可以被阻塞,之后等待被唤醒
    if (ws == Node.SIGNAL)
        return true;
    if (ws > 0) {
        // 如果ws > 0,说明当前节点已经被取消,因此循环剔除ws>0的前驱节点
        do {
            node.prev = pred = pred.prev;
        } while (pred.waitStatus > 0);
        pred.next = node;
    } else {
        //如果ws<=0,则将标志位设置为Node.SIGNAL,当还不可被阻塞,需要的等待下次执行shouldParkAfterFailedAcquire判断是否需要阻塞
        compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
    }
    return false;
}

3 unparkSuccessor in question if (s == null || s.waitStatus> 0) {...}, why determination waitStatus> 0

During execution, waitStatus first successor node of the head node> 0 is the node is canceled, when it is possible to acquire the lock because the timeout is canceled, so we need to skip the node, so the need to find the next node to be woken up, and if a successor node of the first waitStatus head node <= 0 direct wake.

Question 4 Why do we need to move forward after traversing thread wakes up to find the nearest and waitStatus <= successor node of the head node 0, can not traverse it from front to back?

We need to revisit this issue on a one way method of addWaiter with enq

addWaiter method
private Node addWaiter(Node mode) {// 首先尝试快速添加到队尾,失败再正常执行添加到队尾
    Node node = new Node(Thread.currentThread(), mode);
    // 快速方式尝试直接添加到队尾
    Node pred = tail;
    if (pred != null) {
        node.prev = pred;
        if (compareAndSetTail(pred, node)) {
            pred.next = node;
            return node;
        }
    }
    // 如果快速添加到队尾失败则执行enq(node)添加到队尾
    enq(node);
    return node;
}
enq method
private Node enq(final Node node) {
    for (;;) {
        Node t = tail;
        if (t == null) { // Must initialize
            if (compareAndSetHead(new Node()))
                tail = head;
        } else {
            // 添加到队列尾部
            node.prev = t;
            if (compareAndSetTail(t, node)) {
                t.next = node;
                return t;
            }
        }
    }
}

Analysis of these two methods may be taken to add the key portion of the tail node of the node synchronization teams

node.prev = pred; // 步骤1
if (compareAndSetTail(pred, node)) {
    pred.next = node; // 步骤2
    return node;
}

From the above it can be seen during the execution of the code insertion node is always executed first node.prev = pred, then perform pred.next = node, so on answers 4 can be explained:

If we re-iterate back then, then under concurrent environment If you add a new node node.prev = pre words may have been performed, but pred.next = node has not been performed, but this time have already begun the implementation of a unparkSuccessor method, so It will lead the newly added nodes may not be traversed, but if it is traversed from the back, then there would be the problem.

These are the AQS exclusive lock release process, if you have any questions, please feel free to correct me you.

Guess you like

Origin www.cnblogs.com/zyg-coding/p/12057392.html