AQS 全称是
AbstractQueuedSynchronizer
, 是 jdk 中用来实现锁的基础框架, 比如 ReentrantLock,ReadWriteLock 以及 Condition 的实现和 AQS 密切相关. 说到 AQS, 等来介绍一下 CLH 锁, CLH 锁是用来实现自旋锁的一种方式, 其大概原理是用一个队列把等待锁的线程保存起来, 自旋等待. 但是 CLH 比较特别的地方在于, 在某种程度上, 每个节点的行为是由其前驱节点的状态来决定的, 如下图所示:
其中 Node 1 代表持有锁的线程, Node 2 和 Node 3 代表等待锁的线程, 每个 Node 含有一个 prev 字段和一个 status 字段, prev 字段指向队列中的前一个节点, status 字段代表当前节点的状态 (是否已经释放了锁). 每个 Node 初始化时 status 为 true, 表示需要获取锁, 当获取到锁并且释放了锁之后 status 变成 false, 这样其后继节点检测到
prev.status == false
的时候就表示轮到自己来操作了, 也就是自己获得了锁.
CLH 队列入队的操作需要一个原子的操作, 假设新建了一个节点 node:
- do {
- prev = tail;
- } while(!tail.compareAndSet(prev, node))
出队的时候需要检测其前驱节点的 status 字段, 满足条件后把队列的 head 节点设置成当前节点即可:
- while(prev.status != false);
- head = node;
CLH 锁的一个好处是入队和出队操作都非常快, 并且不需要加锁, 而且检测是否有线程正在等待也非常快 (只需判断队列的 head 是否等于 tail). 另外 CLH 队列把状态分散到各个节点, 这样就避免了一些内存方面的数据竞争.
在某个节点的前驱节点因为超时或者其它原因被取消的时候, 可以用该前驱节点的 prev 字段来找到一个没有被取消的节点的 status 来判断当前节点是否持有锁.
CLH 锁由于每个线程都是自旋等待, 并且不停地轮询另一个线程的状态, 所以在 NUMA 架构的处理器上面可能造成性能损失, 在这种情况下可以使用 MCS 锁, 本文就不介绍了.
AQS 使用了类似的队列来放置等待的线程, 但是和 CLH 锁不一样的地方有几点, 首先 CLH 锁队列中的每个线程都是自旋等待, 而 AQS 队列中的线程一般是阻塞等待的. 另外 AQS 队列的 Node 类包含的信息要比 CLH 队列的 Node 类丰富, 这也是意料之中的, 下面来介绍一下 AQS 的 Node 类:
- static final class Node {
- // 以下定义了 Node 类 waitStatus 字段的几种状态
- static final int CANCELLED = 1;
- static final int SIGNAL = -1;
- static final int CONDITION = -2;
- static final int PROPAGATE = -3;
- // 该 Node 对象的状态, 用于控制自身和其后继节点的行为
- volatile int waitStatus;
- // 前驱节点
- volatile Node prev;
- // 后继节点
- volatile Node next;
- // 该 Node 代表的线程
- volatile Thread thread;
- }
上面的 Node 类并不完全, 只是把实现基本功能的字段列举了出来, 没有列出实现共享锁需要的字段. 来看一下 waitStatus:
SIGNAL 表示后继节点正处于或者将要处于被阻塞的状态下, 当前节点释放锁或者被取消的时候需要唤醒后继节点. AQS 的 acquire 方法中会先把节点的前驱节点的状态设置成 SIGNAL 状态, 然后调用 tryAcquire 方法尝试获取锁, 如果失败的话会阻塞该节点.
CANCELLED 表示该节点由于超时或者中断已经被取消. 一个处于取消状态的节点永远都只会是取消状态, 并且其代表的线程不会再阻塞.
CONDITION 表示节点处在 Condition 对象的等待队列上. 这个队列和 AQS 对象的队列是两个队列, Condition 对象的等待队列只有在条件满足的情况下才会转移到 AQS 的等待队列上. 如果没有使用 Condition 接口, 那么这个状态不会出现. 暂时无需考虑这种状态.
另外, AQS 类本身维护了一个 int 类型的状态字段 state, 并且提供了接口用于修改和查询这个状态. state 字段反映的是锁的状态, 是否被某个线程持有, 还是暂时是空闲的. AQS 中还有两个字段:
- private transient volatile Node head;
- private transient volatile Node tail;
分别用于表示 AQS 等待队列的头节点和尾节点.
AQS 也从
AbstractOwnableSynchronizer
类继承了一个字段
private transient Thread exclusiveOwnerThread;
, 指向正持有锁的线程.
来源: https://blog.csdn.net/q_an1314/article/details/79868055