本篇文章紧跟着《并发编程艺术》作者的思路去阅读AQS的整体代码,实现细节。

同步队列Node数据结构

AQS两个主要的成员变量,同步状态和同步队列,同步状态int值没啥说的。同步队列的Node结构还是需要了解一下的
image.png

  • thread:获取同步状态失败的线程
  • prev:前驱节点
  • next:后继节点
  • nextWaiter?
  • waitStatus:
    • CANCELLED:值为1,等待的线程超时、被中断。节点的状态终态
    • SIGNAL:值为-1,后继节点处于等待状态,当前线程释放同步状态,后继节点得以运行
    • CONDITION:值为-2,节点线程等待在Condition上,被其他线程唤醒后,节点转移到同步队列
    • PROPAGATE:值为-3,下一次共享式同步状态获取将无条件的传播下去???
    • INITIAL:值为0 初始状态

      AQS拥有队列的头尾节点

      image.png
      AQS通过CAS的方式设置尾节点
      image.png

独占式同步状态获取与释放

获取

  • 方法:acquire(int arg)
    • 同步状态获取
    • 节点构造
    • 加入同步队列并自旋等待
  • 主要逻辑描述:自定义的同步器,调用其tryAcquire(int arg)方法,保证安全的获取同步状态。若失败,构造同步节点并加入队列的尾部。之后的acquireQueued(Node node,int arg)。自旋获取同步状态。若获取不到则阻塞节点。阻塞线程的唤醒依靠前驱节点的出队或阻塞线程的中断。

    1. public final void acquire(int arg) {
    2. if (!tryAcquire(arg) &&
    3. acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
    4. selfInterrupt();
    5. }
  • 方法:addWaiterenq

    • 快速尝试设置尾节点
    • 失败后enq自旋设置
  • 方法:acquireQueued
    • 节点需要自旋检查前一个节点是否为首节点
    • 是首节点才会继续获取同步状态

image.png

释放

  • 方法:release(int arg)
    • unparkSuccessor()来唤醒后继节点

      总结

      在获取同步状态时,同步器维护一个同步队列,获取状态失败的线程都会被加入到队列中并在队列中进行自旋;移出队列(或停止自旋)的条件是前驱节点为头节点且成功获取了同步状态。在释放同步状态时,同步器调用tryRelease(int arg)方法释放同步状态,然后唤醒头节点的后继节点

共享式同步状态获取与释放

获取

  1. acquireShared(int arg)
    1. tryAcquireShared(int arg) 若大于0获取成功
    2. 小于0获取失败,调用doAcquireShared(int arg)
  2. doAcquireShared(int arg)
    1. 循环等待前驱节点是头结点
    2. tryAcquireShared(int arg)尝试获取

      释放

      释放后,唤醒后续等待状态中的节点

独占式超时获取同步状态

  1. doAcquireNanos(int arg,long nanosTimeout)
    1. 可以响应中断
    2. 超时计算公式:nanosTimeout-=now-lastTime。now是当前、lastTime是上次被唤醒时间

image.png