【Java并发】AQS二:AbstractQueuedSynchronizer同步基础框架内部Node

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/qq_22871607/article/details/89430932

AQS(AbstractQueuedSynchronizer)是java同步的基本框架,依赖于先进先出(FIFO)等待队列的阻塞锁和相关同步器(信号量、事件等),我们常用的ReentrantLock,Semaphore,
CountDownLatch等内部均是实现AQS的同步机制,因此要深入理解java同步机制,先从理解AQS框架开始。

AQS是一个抽象类这个类继承AbstractOwnableSynchronizer, AbstractOwnableSynchronizer这个类用于设置当前线程的辅助类。

AQS内部维护了一个结点队列,队列上维护的是获取同步上锁的线程,了解AQS先从了解这些Node的定义开始。
AQS内部定义Node源码:

/**
 * 等待队列节点类
 *
 * 等待队列是“CLH”(Craig、Landin和Hagersten)锁队列的变体。CLH锁通常用于自旋锁。相反,我们使用它们来阻塞同步器,
 * 但是使用相同的基本策略,即在其节点的前身中保存关于线程的一些控制信息。每个节点中的“status”字段跟踪线程是否应该阻塞。
 * 节点在其前任节点释放时发出信号。否则,队列的每个节点都充当一个特定的通知样式的监视器,其中包含一个等待线程。
 * 状态字段不控制线程是否被授予锁等。一个线程可能试图获取队列中的第一个线程。
 * 但领先并不能保证成功;它只给予竞争的权利。因此,当前发布的竞争者线程可能需要重新等待。
 *
 * 要加入到CLH锁中,您可以原子地将其拼接为new tail。要退出队列,只需设置head字段。
 *
 *      +------+  prev +-----+       +-----+
 * head |      | <---- |     | <---- |     |  tail
 *      +------+       +-----+       +-----+
 *
 * 插入CLH队列只需要在“tail”上执行一个原子操作,因此存在一个简单的原子点,用于从未排队到排队的划分。类似地,
 * 退出队列只涉及更新“head”。然而,节点需要做更多的工作来确定谁是他们的继任者,部分原因是处理由于超时和中断而可能发生的取消。
 *
 * “prev”链接(在原来的CLH锁中没有使用)主要用于处理取消。如果一个节点被取消,它的继承节点(通常)会重新链接到
 *  一个未取消的继承节点。有关自旋锁的类似机制的解释,请参阅Scott和Scherer的论文at:
 *  http://www.cs.rochester.edu/u/scott/synchronization/
 *
 * 我们还使用“next”链接来实现阻塞机制。每个节点的线程id都保存在它自己的节点中,因此,前一个节点通过遍历
 * 下一个链接来通知下一个要唤醒的节点,以确定它是哪个线程。继任者的确定必须避免使用新排队的节点来设置其前任的“下一个”字段的竞争。
 * 当节点的后继出现null时,
 * 通过从原子更新的“tail”向后检查,可以在必要时解决这个问题。(或者,换句话说,next-links是一种优化,因此我们通常不需要反向扫描。)
 *
 * 对消在基本算法中引入了一些保守性。因为我们必须轮询其他节点的取消,所以我们可能会忽略被取消的节点是在前面还是在后面。处理
 * 这一问题的方法是,总是在取消时取消后继者的泊车位,使他们能够稳定地依靠新的前任,除非我们能够确定将承担这一责任的未取消的前任。
 *
 * CLH队列需要一个虚拟头节点来启动。但是,我们不会在构建过程中创建它们,因为如果从来没有争用,
 * 就会浪费精力。相反,将构造节点,并在第一次争用时设置head和tail指针。
 *
 * 等待条件的线程使用相同的节点,但使用额外的链接。条件只需要在简单(非并发)链接队列中链接节点,因为它们只在独占时才被访问。
 * 在等待时,将节点插入到条件队列中。信号一发出,节点就被转移到主队列。状态字段的一个特殊值用于标记节点所在的队列。
 *
 * 感谢Dave Dice、Mark Moir、Victor Luchangco、Bill Scherer和Michael Scott,以及JSR-166专家组的成员,
 * 为本课程的设计提供了有用的想法、讨论和批评。
 */
static final class Node {

    /** 指示节点在共享模式下等待的标记 */
    static final Node SHARED = new Node();
    /** 标记指示节点在独占模式下等待 */
    static final Node EXCLUSIVE = null;

    /** waitStatus的值表示线程已被取消 */
    static final int CANCELLED =  1;
    /** waitStatus的值指示唤醒等待中的后续线程 */
    static final int SIGNAL    = -1;
    /** waitStatus的值表示条件等待 */
    static final int CONDITION = -2;
    /** waitStatus的值表示下一个acquireShared()应该无条件传播 */
    static final int PROPAGATE = -3;

    /**
     * 状态值,只接受以下值:
     *   SIGNAL:     此节点的后继节点(或将很快)被阻塞(通过park),因此当前节点在释放或取消时必须取消对其后继节点的存储。
     *               为了避免争用,获取方法必须首先表明它们需要一个信号,然后重试原子获取,然后在失败时阻塞
     *   CANCELLED:  由于超时或中断,此节点被取消。节点永远不会离开此状态。特别是,具有已取消节点的线程再也不会阻塞。
     *   CONDITION: 此节点当前位于条件队列中。在传输之前,它不会被用作同步队列节点,此时状态将被设置为0。
     *              (这里这个值的使用与场的其他用途无关,而是简化了力学。)
     *   PROPAGATE:  已发布的节点应该传播到其他节点。这在doReleaseShared中设置(仅针对head节点),以确保传播继续进行,
     *              即使其他操作已经介入。
     *   0:          以上皆非
     *
     * 数值的排列是为了简化使用。非负值表示节点不需要发出信号。因此,大多数代码不需要检查特定的值,只需要检查符号。
     *
     * 对于普通同步节点,字段初始化为0,对于条件节点,字段初始化为条件。使用CAS(或者在可能的情况下,使用无条件的volatile写)修改它。
     */
    volatile int waitStatus;

    /**
     * 链接到当前节点/线程所依赖的用于检查等待状态的前辈节点。在排队时分配,只有在退出排队时才为空(为了GC)。
     * 此外,在取消前一个节点时,我们会在找到一个未取消的节点时短路,因为头节点从来没有被取消过:一个节点只有在成功获取后才成为头节点。
     * 被取消的线程永远不会成功获取,并且线程只取消自己,而不取消任何其他节点。
     */
    volatile Node prev;

    /**
     * 链接到当前节点/线程在发布时解压的后续节点。在排队过程中分配,在绕过已取消的前辈时进行调整,在退出队列时为空(为了GC)。
     * enq操作直到附件之后才分配前任的下一个字段,所以看到一个空的next字段并不一定意味着节点在队列的末尾。然而,
     * 如果下一个字段显示为空,我们可以从尾部扫描prev以进行双重检查。
     * 取消节点的下一个字段被设置为指向节点本身,而不是null,以简化isOnSyncQueue的工作。
     */
    volatile Node next;

    /**
     * 进入此节点队列的线程。在构造时初始化,使用后为空。
     */
    volatile Thread thread;

    /**
     * 链接到正在等待状态的下一个节点,或共享的特殊值。因为条件队列只有在独占模式下才能访问,
     * 所以我们只需要一个简单的链接队列来在节点等待条件时保存节点。
     * 然后将它们转移到队列中重新获取。由于条件只能是独占的,所以我们使用特殊值来表示共享模式来保存字段。
     */
    Node nextWaiter;

    /**
     * 如果节点在共享模式下等待,则返回true。
     */
    final boolean isShared() {
        return nextWaiter == SHARED;
    }

    /**
     * 返回以前的节点,如果为空则抛出NullPointerException。当前任不能为空时使用。可以省略null检查,但它是用来帮助VM的。
     *
     * @return 这个结点的前驱结点
     */
    final Node predecessor() throws NullPointerException {
        Node p = prev;
        if (p == null)
            throw new NullPointerException();
        else
            return p;
    }

    Node() {    // 用于建立初始头结点或共享标记
    }

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

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

这个类Node被AbstractQueuedSynchronizer使用,用于在获取锁时对当前等待线程的排队机制。

展开阅读全文

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