Java并发之AQS与ConditionObject

610 阅读3分钟

前言

AQS大致结构图

image.png

  • 前面讲解的可重入锁和可重入读写锁都是围绕着阻塞队列讲解的。
  • 没有提到AQS中的另一个重要内容:等待队列,也称之为条件(condition)队列。
  • AQS有Node对象,其有两个用途:形成等待队列和阻塞队列。
  • 虽然是Node,挺像链表的,但是jdk的注释中只用了queues这个单词,所以都称之为队列,毕竟它倾向于FIFO。

以ReentrantLock中的newCondition为例

不厌其烦的阅读jdk源码

newCondition()方法

image.png

关于AQS中的Node的状态

image.png

  • 状态字段,仅取值: SIGNAL:此节点的后继节点被(或即将)阻塞(通过park),因此当前节点在释放或取消时必须解除其后继节点的park。 为了避免竞争,获取方法必须首先表明它们需要一个信号(signal),然后重试原子获取,然后在失败时阻塞。 CANCELLED:由于超时或中断,该节点被取消。 节点永远不会离开这个状态。 特别是,取消节点的线程永远不会再次阻塞。 CONDITION:该节点当前在条件队列中。 它在传输之前不会用作同步队列节点,此时状态将设置为 0。(此处使用此值与该字段的其他用途无关,但简化了机制。) PROPAGATE:A releaseShared 应该传播到其他节点。 这在 doReleaseShared 中设置(仅适用于头节点)以确保传播继续,即使其他操作已经介入。 0:以上都不是 为了简化使用,数值按数字排列。 非负值意味着节点不需要发出信号,通知后继节点。 因此,大多数代码不需要检查特定值,只需检查符号。 对于普通同步节点,该字段被初始化为 0,对于条件节点,该字段被初始化为 CONDITION。 它使用 CAS 修改(或在可能的情况下,无条件 volatile 写入)。

await方法的实现

image.png

其实也没做什么,就创建了等待队列和判断在等待中被中断。

signal方法的实现

image.png

小结

关于AQs与synchronized关键字之间的关系:

  1. synchronized关键字在底层的c++实现中,存在两个重要的数据结构 (集合)︰waitSet,EntryList。
  2. waitSet中存放的是调用了object的wait方法的线程对象(被封装成了C++的Node对象)。
  3. EntryList中存放的是陷入到阻塞状态、需要获取monitor的那些线程对象。
  4. 当一个线程被notify后,它就会从waitset中移动到EntryList中。
  5. 进入到EntryList后,该线程依然需要与其他线程争抢monitor对象。
  6. 如果争抢到,就表示该线程获取到了对象的锁,它就可以以排他方式执行对应的同步代码。

  1. AQS中存在两种队列,分别是Condition对象上的条件队列,以及AQS本身的阻塞队列。
  2. 这两个队列中的每一个对象都是Node实例(里面封装了线程对象),还有节点的状态。
  3. 当位于Condition条件队列中的线程被其他线程signal后,该线程就会从条件队列中移动到AQS的阻塞队列中。
  4. 位于AQS阻塞队列中的Node对象本质上都是由一个双向链表来构成的。
  5. 在获取AQS锁时,这些进入到阻塞队列中的线程会按照在队列中的排序先后尝试获取。
  6. 当AQS阻塞队列中的线程获取到锁后,就表示该线程已经可以正常执行了。
  7. 陷入到阻塞状态的线程,依然需要进入到操作系统的内核态,进入阻塞(park方法实现)。

waitSet对应AQS等待队列,且后者可以多个,且互不干扰。
EntryList对应AQS的阻塞队列。