zoukankan      html  css  js  c++  java
  • AbstractQueuedSynchronizer同步队列与Condition等待队列协同机制

    概要

    AQS维护了一个同步队列

    Condition是JUC的一个接口,AQS的ConditionObject实现了这个接口,维护了一个等待队列(等待signal信号的队列)

     线程调用reentrantLock.lock()时,线程被加入到AQS同步队列中;

    线程A调用condition.await()方法时,线程A从AQS同步队列中被移除,被加入到Condition等待队列,等待signal信号

    线程B调用signal()方法,Condition等待队列中有一个节点A,把它取出来(A)加入到AQS同步队列中。这时候线程A并没有被唤醒(signal可以指定唤醒哪个condition)

    只有发送singal信号的线程调用reentrantLock.unLock()后,因为它(线程A)已经被加入到AQS同步队列并且成为同步队列头结点,所以线程才会被唤醒。

    之前对AbstractQueuedSynchronizer(AQS)同步队列与Condition等待队列的功能一直不是很清晰,没太清楚地区分开二者的区别和联系,最近研究了一下分享出来。

    例子分析参见另一篇文章:【JUC】Condition和生产者消费者模型  https://www.cnblogs.com/twoheads/p/9591874.html

    1.同步队列和等待队列简述

    说明:AbstractQueuedSynchronizer类底层的数据结构是使用链表,是队列的一种实现,故也可看成是队列,其中Sync queue,即同步队列,是双向链表,包括head结点和tail结点,head结点主要用作后续的调度。而Condition queue即等待队列不是必须的,其是一个单向链表,只有当使用Condition时,才会存在此单向链表。并且可能会有多个Condition queue。Condition的具体实现类是AQS的内部类ConditionObject

    我们知道AQS自己维护的队列是当前等待资源的队列,当前线程获取同步状态失败时,同步器会将当前线程以及等待状态等信息构造成为一个节点并将其加入同步队列,同时会阻塞当前线程,当同步状态释放时,会把首节点中的线程唤醒,使其再次尝试获取同步状态。

    而Condition自己也维护了一个等待队列(等待signal信号的队列),两个队列的作用是不同,事实上,每个线程也仅仅会同时存在以上两个队列中的一个



    每个Condition维护着一个队列,该队列的作用是维护一个等待signal信号的队列。


        //AQS中的Node属性
        static final class Node {
            ...
            volatile int waitStatus;//等待状态
            volatile Node prev;//前驱节点
            volatile Node next;//后驱节点
            volatile Thread thread;//获取同步状态的线程,当前执行线程
            Node nextWaiter;//等待队列中的后继节点
            ...
        }

    从上述Node结构可以看出,其实同步队列和等待队列使用的是同一个Node类型AbstractQueuedSynchronizer.Node。

    2.同步队列和等待队列区别与协同机制

    下面我们举一个具体的例子来说明同步队列和等待队列之间的区别与协同工作:

    1. 同步队列的初始状态为下图,同步队列中包含线程A(节点A)和线程B(节点B),线程调用reentrantLock.lock()时,线程被加入到AQS同步队列中

    2. 线程A(节点A)调用condition.await()方法时,线程A(节点A)从AQS同步队列中被移除,对应操作是锁的释放; 线程A(节点A)接着被加入到Condition等待队列,因为线程需要signal信号。
    3. 线程B(节点B)由于线程A(节点A)释放锁被唤醒,判断成为同步队列头结点且同步状态为0可以获取锁;线程B(节点B)获取锁。

    4. 线程B(节点B)调用signal()方法,Condition等待队列中有一个节点A,把它取出来(A)加入到AQS同步队列中。这时候线程A(节点A)并没有被唤醒。

    5. 线程B(节点B)signal方法执行完毕,并调用reentrantLock.unLock()方法释放锁。线程A(节点A)成为AQS首节点并且同步状态可获取,线程A(节点A)被唤醒,继续执行。
    6. AQS从头到尾顺序唤醒线程,直到等待队列中的线程被执行完毕结束。

    只有发送singal信号的线程调用reentrantLock.unLock()后,因为它已经被加入到AQS同步队列并且成为同步队列头结点,所以线程才会被唤醒。

    转:

    https://blog.csdn.net/tb3039450/article/details/69056169

    http://ifeve.com/understand-condition/

  • 相关阅读:
    Effective Java 第三版——26. 不要使用原始类型
    Effective Java 第三版——25. 将源文件限制为单个顶级类
    Effective Java 第三版——24. 优先考虑静态成员类
    Effective Java 第三版——23. 优先使用类层次而不是标签类
    Effective Java 第三版——22. 接口仅用来定义类型
    Effective Java 第三版——21. 为后代设计接口
    Effective Java 第三版——20. 接口优于抽象类
    Effective Java 第三版——19. 如果使用继承则设计,并文档说明,否则不该使用
    Effective Java 第三版——18. 组合优于继承
    Effective Java 第三版——17. 最小化可变性
  • 原文地址:https://www.cnblogs.com/twoheads/p/9593262.html
Copyright © 2011-2022 走看看