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

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

首先说明一下CLH队列

  1. 下面是AQS中对Node节点的注释,然后看看CLH队列
  • CLH(Craig, Landin, andHagersten)根据三个人的名字命名的队列
  • 它是队列锁的一个变种(variant),通常用于自旋锁(这个源码中会有体现),用它代替阻塞同步器
  • 在节点中用"status"字段来跟踪每个节点是否处于阻塞状态
  • 每个节点在在其前面一个节点释放同步状态后,前驱节点将会通知该节点去获取同步状态--即:发出信号通知该节点
  • CLH队列中的每个节点中的线程都有机会尝试获取同步状态,但是并不能保证一定获取成功
  • CLH是一个双向链表队列,(就数据结构而言,算是比较简单的数据结构了,相对其他的数据结构来说)
  • 加入到CLH队列中的节点会被加入到队列的尾部,并保证加入时的原子性
  • 节点弹出队列的时候只需要将头节点head移除即可
  • 节点在确定它的前驱节点时,需要耗费一定的操作,因为要处理部分节点被取消或者中断
  • 队列中的每个节点都有一个唯一的线程,用该线程来获取同步状态
     * Wait queue node class.
     *
     * 

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. * *

To enqueue into a CLH lock, you atomically splice it in as new * tail. To dequeue, you just set the head field. *

     *      +------+  prev +-----+       +-----+
     * head |      | <---- |     | <---- |     |  tail
     *      +------+       +-----+       +-----+
     * 
* *

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. * *

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/ * *

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.) * *

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. * *

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. * *

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. * *

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、我只是不想那么轻易的认输

你可能感兴趣的:(AQS(队列同步器)CLH中的节点入队源码(二))