队列同步器的分析

包括:同步队列,独占式同步状态获取与释放、共享式同步状态获取与释放
1.同步队列
AQS依赖内部的同步队列(一个FIFO双向队列)来完成同步状态管理。当线程获取同步状态失败时,同步器会将当前线程和等待状态等信息包装成一个节点并将其加入同步队列,同时会阻塞当前线程。当同步状态释放时,会把首节点中的线程唤醒,使其再次尝试获取同步状态。
节点是构成同步队列的基础,同步器拥有首节点和尾节点,没有成功获取同步状态的线程会成为节点加入该队列的尾部,其结构如下图所示

上图中:同步器包含了两个节点类型的引用,一个纸箱头结点,另一个指向尾节点。假设其中一个线程获取了(独占)锁,则其他线程需要将其等待状态等信息构造成为一个节点(Node)将其存入同步队列。如何安全的加入至队列??通过基于CAS的方法compareAndSetTail(Node expect, Node update)来实现原子性操作。
将等待线程中的信息构造成节点保存至同步队列

同步队列遵循FIFO,首节点是获取成功状态的节点,首节点的线程在释放同步状态时,将会唤醒后继节点,然后后继节点获取同步状态时将自己设置为首节点。并断开原节点的后继节点。`
这里写图片描述
因为只有一个线程获取到同步状态,故不需要进行原子操作。
2.独占式同步状态获取与释放
AQS的acquire(int arg)可以获取同步队列,该方法对中断不敏感,线程不会从同步队列中移除。这里面主要完成的工作是同步状态获取、节点构造、加入同步队列以及在同步队列中自旋等操作,其主要逻辑是:
(1)调用自定义同步器的tryAcquire(int arg)方法,该方法保证线程安全的获取同步状态
(2)如果获取失败,就构造一个独占式(Node.EXCLUSIVE)的同步节点,并通过addWaiter方法加入到同步节点的尾部
(3)最后调用acquiredQueued方法,是的该节点以“死循环”的方式获取同步状态。如果获取不到则阻塞节点中的线程,而被阻塞线程的唤醒主要依靠前驱节点的出队或阻塞线程中断来实现。

//先看上面的addNode方法
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;
                }
            }
        }
    }

在enq(final Node node)方法将并发添加节点的请求通过通过CAS变得“串行化”了。

展开阅读全文

没有更多推荐了,返回首页