JUC--AQS源码分析(一)CLH同步队列

1 概述

了解过JUC的源码,我们就可以知道JUC下面很多工具的实现都是依靠AQS,而AQS中用于保存等待线程的队列就是CLH。CLH是一个FIFO的队列。队列的每一个节点都是一个Node对象。当前线程获取同步状态失败的时候就会进入CLH队列。而当同步状态被释放的时候会通知首节点再次去获取同步状态。

2 Node节点

首先我们来看看Node节点的实现到底是怎么回事,源码如下:

static final class Node {
        /** 共享模式 */
        static final Node SHARED = new Node();
        /** 独占模式 */
        static final Node EXCLUSIVE = null;

        /** 因为超时或者是中断,节点会处于删除状态,处于删除状态的节点不会转变成其他状态
         *  ,会一直处于删除状态
         */
        static final int CANCELLED =  1;
        /** 等待状态,用于表示后继节点是否需要被唤醒 */
        static final int SIGNAL    = -1;
        /** 节点再等待队列中,节点的线程将会处于Condition等待状态,只有其他线程调用了Condition
         *  的signal()后,该节点由等待队列进入到同步队列,尝试获取同步状态
         */
        static final int CONDITION = -2;
        /**
         * 表示下一次共享式同步状态获取将会无条件传播下去
         */
        static final int PROPAGATE = -3;

        /**
         * 等待状态:
         *   SIGNAL:     表示这个节点的后继节点被阻塞,到时需要通知它。
         *   CANCELLED:  由于中断和超市,这个节点处于被删除的状态。处于被删除状态的节点不会转 
         *               换成其他状态,这个节点将被踢出同步队列,被GC回收。
         *   CONDITION:  表示这个节点再条件队列中,因为等待某个条件而被阻塞。
         *   PROPAGATE:  使用在共享模式头结点有可能牌处于这种状态,表示锁的下一次获取可以无条件 
         *               传播.
         *   0:          新节点会处于这种状态。
         *
         */
        volatile int waitStatus;

        /**
         * 队列中,节点的前一个节点
         */
        volatile Node prev;

        /**
         * 节点的后继节点.
         */
        volatile Node next;

        /**
         * 节点所拥有的线程.
         */
        volatile Thread thread;

        /**
         * 条件队列中,节点的下一个等待节点.
         */
        Node nextWaiter;

        /**
         * 判断节点时候是共享模式.
         */
        final boolean isShared() {
            return nextWaiter == SHARED;
        }

        /**
         * 获取前一个节点
         */
        final Node predecessor() throws NullPointerException {
            Node p = prev;
            if (p == null)
                throw new NullPointerException();
            else
                return p;
        }

        Node() {    // Used to establish initial head or SHARED marker
        }

        Node(Thread thread, Node mode) {     // Used by addWaiter
            this.nextWaiter = mode;
            this.thread = thread;
        }

        Node(Thread thread, int waitStatus) { // Used by Condition
            this.waitStatus = waitStatus;
            this.thread = thread;
        }
    }

针对节点中的每个属性所代表的意思,已经在注释中给出了详细的解释。下面我们来看看CLH同步队列结构图。

《JUC--AQS源码分析(一)CLH同步队列》

3 入列

通过CLH同步队列结构图我们基本可以猜到,CLH同步队列入列是怎么回事。就是将当前同步队列的最后一个节点的next指向添加的节点,并将添加节点的prev指向最后一个节点。同时需要将tail指向新添加的节点。下面我们来查看一下具体的方法addWaiter(Node mode)的源码。

private Node addWaiter(Node mode) {

        //新建node
        Node node = new Node(Thread.currentThread(), mode);
        
        //快速尝试添加尾节点
        Node pred = tail;
        if (pred != null) {
            node.prev = pred;
            
            //使用cas设置尾节点
            if (compareAndSetTail(pred, node)) {
                pred.next = node;
                return node;
            }
        }

        //循环设置尾节点
        enq(node);
        return node;
    }

在addWaiter方法中我们会尝试获取尾节点并进行尾节点的设置,如果成功就直接返回,如果没有成功就调用enq(final Node node)进行设置,下面我们来看看enq(final Node node)方法的具体实现。

private Node enq(final Node node) {
       
       //死循环尝试,直到成功 
       for (;;) {
            Node t = tail;

            //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;
                }
            }
        }
    }

我们可以看见上面使用了CAS来进行首节点和尾节点的设置,以达到线程安全的目的。只有尾节点设置成功了才会返回,否则会一直进行下去。过程图如下:

《JUC--AQS源码分析(一)CLH同步队列》

那么如果是出列呢?

4  出列

CLH同步队列遵循FIFO的规则,首节点的线程释放同步状态后。将会唤醒后继结点next,而后继节点将会尝试获取同步状态,如果获取同步状态成功,将会将自己设置成首节点。同时需要注意的是,这个过程后继节点会断开需前节点的关联,具体流程形如下图。

《JUC--AQS源码分析(一)CLH同步队列》

    原文作者:JUC
    原文地址: https://blog.csdn.net/ONROAD0612/article/details/81627133
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞