Java高并发高性能编程(多线程,协程,Actor,RxJava、Akka、Reactor)

AQS(队列同步器)CLH中的节点入队源码(二)

2018-06-21  本文已影响61人  T_log

根据目前对队列同步器的理解,先把目前对AQS-队列同步器认识记录一下。然后根据源码的注释,一点点进行解析

首先说明一下CLH队列

  1. 下面是AQS中对Node节点的注释,然后看看CLH队列
     * Wait queue node class.
     *
     * <p>The wait queue is a variant of a "CLH" (Craig, Landin, and
     * Hagersten) lock queue. CLH locks are normally used for
     * spinlocks.  We instead use them for blocking synchronizers, but
     * use the same basic tactic of holding some of the control
     * information about a thread in the predecessor of its node.  A
     * "status" field in each node keeps track of whether a thread
     * should block.  A node is signalled when its predecessor
     * releases.  Each node of the queue otherwise serves as a
     * specific-notification-style monitor holding a single waiting
     * thread. The status field does NOT control whether threads are
     * granted locks etc though.  A thread may try to acquire if it is
     * first in the queue. But being first does not guarantee success;
     * it only gives the right to contend.  So the currently released
     * contender thread may need to rewait.
     *
     * <p>To enqueue into a CLH lock, you atomically splice it in as new
     * tail. To dequeue, you just set the head field.
     * <pre>
     *      +------+  prev +-----+       +-----+
     * head |      | <---- |     | <---- |     |  tail
     *      +------+       +-----+       +-----+
     * </pre>
     *
     * <p>Insertion into a CLH queue requires only a single atomic
     * operation on "tail", so there is a simple atomic point of
     * demarcation from unqueued to queued. Similarly, dequeuing
     * involves only updating the "head". However, it takes a bit
     * more work for nodes to determine who their successors are,
     * in part to deal with possible cancellation due to timeouts
     * and interrupts.
     *
     * <p>The "prev" links (not used in original CLH locks), are mainly
     * needed to handle cancellation. If a node is cancelled, its
     * successor is (normally) relinked to a non-cancelled
     * predecessor. For explanation of similar mechanics in the case
     * of spin locks, see the papers by Scott and Scherer at
     * http://www.cs.rochester.edu/u/scott/synchronization/
     *
     * <p>We also use "next" links to implement blocking mechanics.
     * The thread id for each node is kept in its own node, so a
     * predecessor signals the next node to wake up by traversing
     * next link to determine which thread it is.  Determination of
     * successor must avoid races with newly queued nodes to set
     * the "next" fields of their predecessors.  This is solved
     * when necessary by checking backwards from the atomically
     * updated "tail" when a node's successor appears to be null.
     * (Or, said differently, the next-links are an optimization
     * so that we don't usually need a backward scan.)
     *
     * <p>Cancellation introduces some conservatism to the basic
     * algorithms.  Since we must poll for cancellation of other
     * nodes, we can miss noticing whether a cancelled node is
     * ahead or behind us. This is dealt with by always unparking
     * successors upon cancellation, allowing them to stabilize on
     * a new predecessor, unless we can identify an uncancelled
     * predecessor who will carry this responsibility.
     *
     * <p>CLH queues need a dummy header node to get started. But
     * we don't create them on construction, because it would be wasted
     * effort if there is never contention. Instead, the node
     * is constructed and head and tail pointers are set upon first
     * contention.
     *
     * <p>Threads waiting on Conditions use the same nodes, but
     * use an additional link. Conditions only need to link nodes
     * in simple (non-concurrent) linked queues because they are
     * only accessed when exclusively held.  Upon await, a node is
     * inserted into a condition queue.  Upon signal, the node is
     * transferred to the main queue.  A special value of status
     * field is used to mark which queue a node is on.
     *
     * <p>Thanks go to Dave Dice, Mark Moir, Victor Luchangco, Bill
     * Scherer and Michael Scott, along with members of JSR-166
     * expert group, for helpful ideas, discussions, and critiques
     * on the design of this class.
     */

CLH队列示意图(画的很粗糙)


CLH入队出队.jpg

队列入队API

    /**
     * Creates and enqueues node for current thread and given mode.
     * 新节点的创建并入队在制定模式下(共享式和独占式)
     * @param mode Node.EXCLUSIVE for exclusive, Node.SHARED for shared
     * @return the new node
     */
      private Node addWaiter(Node mode) {
        //1、为当前线程创建新节点
        Node node = new Node(Thread.currentThread(), mode);
        // 尝试快速插入,如果失败则用enq插入

        //1、获取同步器中的tail指向的节点,即:未插入新节点时的尾节点(暂且称之为原队列中的尾节点)
        Node pred = tail;
        if (pred != null) {//判断尾节点不为空,为空则使用enq插入,enq中会存在创建head和Tail节点的逻辑
            //2、新节点的前驱节点指向原尾节点
            node.prev = pred;
            //3、使用CAS设置尾节点(AQS代码风格之一:将操作放入if判断中)
            if (compareAndSetTail(pred, node)) {
                //4、将原尾节点的next指向新节点
                pred.next = node;
                return node;
            }
        }
        //如果快速插入队列失败,则用enq进行插入
        enq(node);
        return node;
    }


/**
     * Inserts node into queue, initializing if necessary. See picture above.
     * 将节点插入队列,如果有必要(未节点为空),即:队列为空,则初始化队列
     * @param node the node to insert
     * @return node's predecessor
     */
    private Node enq(final Node node) {
        //类似节点获取同步状态时的自旋,其实就是有返回条件的死循环
        for (;;) {
            //1、将同步器中的未节点赋给临时变量t
            Node t = tail;
            if (t == null) { // Must initialize
                //2、如果未节点为空,就是队列为空,则说明队列为空,新建一个节点,使用CAS设置头结点。,CAS保证操作的原子性
                if (compareAndSetHead(new Node()))
                    //3、如果头结点设置成功,则将同步器中的未节点指向头结点,然后继续步骤1
                    tail = head;
            } else {
                //4、将新节点的前驱节点指向原队列中的未节点
                node.prev = t;
                //5、使用CAS设置未节点,即:同步器中的tail指向新节点
                if (compareAndSetTail(t, node)) {
                    //6、如果未节点设置成功,则将原未节点的next指向新节点
                    t.next = node;
                    return t;
                }
            }
        }
    }
 ---    
1、不知道随着AQS的深入研究,回过头来看看现在对源码的理解,是否会笑
2、时刻记录自己的想法,就算错了,以后也知道我为什么错了,我哪里错了,我为什么会错
3、我只是不想那么轻易的认输
上一篇 下一篇

猜你喜欢

热点阅读