Java Concurrent AQS Principle Analysis (2)

The previous article analyzed in detail how to process threads in exclusive mode: a simple summary is that Java provides a lock mechanism for users, and the latter implementation uses a synchronous queue. Threads are constructed as nodes in the queue. Each node defines a state value. Only nodes (threads) that meet the state can have the opportunity to execute. After the execution is released, the subsequent threads can only wait for the previous execution results to judge. The execution of each thread is independent and cannot be interfered by other threads, so from the user's point of view, the threads are executed synchronously and exclusively.

The difference between shared and exclusive is mainly whether different threads can obtain the synchronization state at the same time. The
write picture description here
above figure can intuitively see the difference between exclusive and shared. When accessing a resource, read operations are shared, and write operations are exclusive.

Next, let's analyze how threads are implemented in shared mode?
We still find the entry of method execution. In the previous article, we found the top-level methods of these methods.

Obtaining the synchronization status of the shared mode:

In the acquireShared()method, the call tryAcquireShared()returns a status value, makes a judgment, and returns directly if it succeeds, and enters the waiting queue if it fails.

public final void acquireShared(int arg) {
        if (tryAcquireShared(arg) < 0)
            doAcquireShared(arg);
    }

The order of method execution:

  1. The method is called tryAcquireShared()to try to obtain resources. The specific implementation is implemented in the subclass, and it returns directly if it succeeds. If it fails, the following method is executed.
  2. When the method is called doAcquireShared(), the thread enters the waiting queue, waiting to acquire resources.

tryAcquireShared()Methods are implemented in subclasses and need not be discussed here, but the return value is already defined. The method returns a value of type int. When the return value is greater than 0, it means that the synchronization state can be obtained.

doAcquireShared() method:
The previously analyzed methods are no longer analyzed here

private void doAcquireShared(int arg) {
        //调用addWaiter方法把线程加入队列尾
        final Node node = addWaiter(Node.SHARED);
        //设置成功标识
        boolean failed = true;
        try {
            //设置中断标识
            boolean interrupted = false;
            //死循环
            for (;;) {
                //获得前驱结点
                final Node p = node.predecessor();
                //如果当前结点的前驱结点是头结点
                if (p == head) {
                    //尝试获取资源
                    int r = tryAcquireShared(arg);
                    if (r >= 0) {
                        //如果获取到资源
                        setHeadAndPropagate(node, r);
                        //设置指向空,帮助回收
                        p.next = null; // help GC
                        if (interrupted)
                            //如果被中断过,重新设置中断
                            selfInterrupt();
                        failed = false;
                        return;
                    }
                }
                //判断线程状态移除或者挂起
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

Basically the same as the exclusive processing method, the fundamental manifestation is in the following setHeadAndPropagate()method
setHeadAndPropagate():

private void setHeadAndPropagate(Node node, int propagate) {
        Node h = head; 
        setHead(node);
        //如果还有资源剩余,继续唤醒后面挨着的线程
        if (propagate > 0 || h == null || h.waitStatus < 0) {
            Node s = node.next;
            if (s == null || s.isShared())
                doReleaseShared();
        }
    }

In the exclusive mode to obtain resources, even if there are remaining resources, the next thread node in the queue will not be awakened for execution, only if it owns it is exclusive, and the shared type will give it back if there is any remaining.

parkAndCheckInterrupt () :

private final boolean parkAndCheckInterrupt() {
        //让当前对象停止执行,阻塞状态
        LockSupport.park(this);
        //检查中断
        return Thread.interrupted();
    }

cancelAcquire():

private void cancelAcquire(Node node) {
        if (node == null)
            return;
        node.thread = null;
        //获得前驱结点
        Node pred = node.prev;
        //如果状态值大于0
        while (pred.waitStatus > 0)
            //移除当前节点的前驱结点
            node.prev = pred = pred.prev;
        //设置节点状态值为CANCELLED
        node.waitStatus = Node.CANCELLED;
        //如果是尾节点,设置尾节点
        if (node == tail && compareAndSetTail(node, pred)) {
            compareAndSetNext(pred, predNext, null);
        } else {
            int ws;
            //当节点既不是尾节点,也不是头节点的后继节点时,下面的这些判断其实执行的出队操作起作用的就是compareAndSetNext()方法将pred指向后继节点
            if (pred != head &&
                ((ws = pred.waitStatus) == Node.SIGNAL ||
                 (ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) &&
                pred.thread != null) {
                Node next = node.next;
                if (next != null && next.waitStatus <= 0)
                    compareAndSetNext(pred, predNext, next);
            } else {
                //如果node是head的后继节点,则直接唤醒node的后继节点
                unparkSuccessor(node);
            }
            node.next = node; // help GC
        }
    }

Release of shared mode synchronization state:

If the attempt to release the resource is successful, it will wake up the subsequent node in the waiting state

public final boolean releaseShared(int arg) {
        //尝试释放资源
        if (tryReleaseShared(arg)) {
            //唤醒后继节点
            doReleaseShared();
            return true;
        }
        return false;
    }

doReleaseShared():

private void doReleaseShared() {
        for (;;) {
            Node h = head;
            if (h != null && h != tail) {
                int ws = h.waitStatus;
                if (ws == Node.SIGNAL) {
                    //唤醒后继节点
                    if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
                        continue;            
                    unparkSuccessor(h);
                }
                else if (ws == 0 &&
                         !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
                    continue;                
            }
            if (h == head)                   
                break;
        }
    }

The difference between them and the exclusive release is that there is a loop in the shared release method.

Citation: https://www.cnblogs.com/waterystone/p/4920797.html

The process of the above method is also relatively simple, in one sentence: after releasing the resource, wake up the successor. Similar to release() in exclusive mode, but there is one thing to note: tryRelease() in exclusive mode will return true to wake up other threads after the resources are completely released (state=0), which is mainly based on exclusive However, releaseShared() in shared mode does not have this requirement. The essence of shared mode is to control a certain number of threads to execute concurrently, so the thread that owns the resource can wake up the successor and wait for the termination when it releases some resources. point. For example, if the total amount of resources is 13, A(5) and B(7) respectively acquire the resources and run concurrently, and when C(4) comes, there is only 1 resource left and need to wait. A releases 2 resources during operation, then tryReleaseShared(2) returns true to wake up C, C sees that only 3 resources are not enough to continue to wait; then B releases 2 more resources, tryReleaseShared(2) returns true to wake up C, C At first glance, there are 5 enough for my own use, and then C can run with A and B. The tryReleaseShared() of the ReentrantReadWriteLock read lock returns true only when the resource is completely released (state=0), so the custom synchronizer can determine the return value of tryReleaseShared() as needed.


In the shared mode, the remaining resources will be given to the neighbors behind, and they will not be occupied by themselves, which is the fundamental difference from the exclusive mode.

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=324601740&siteId=291194637