AQS 同步器底层设计思考
在并发编程中,AbstractQueuedSynchronizer(简称 AQS)是构建锁和其他同步组件的基础。抛开具体实现,我们先聊聊 AQS 内部几个关键的设计决策,这些往往是面试中的高频考点。
1. state 为什么是 int 类型而不是 boolean?
AQS 既支持独占模式(互斥锁),也支持共享模式(如读写锁、信号量)。如果只用 boolean 表示状态,确实能区分'占用'和'空闲',但无法处理共享资源被多个线程同时持有的情况。int 类型的 state 可以记录持有资源的线程数量,从而灵活支持不同的同步场景。
2. 为什么要引入虚拟的 head 节点?
AQS 维护了一个 FIFO 的双向等待队列。每个节点在休眠前,需要将前置节点的 waitStatus 设置为 SIGNAL(-1),这样当前节点释放锁时才能唤醒后继者。对于刚插入的新节点,它没有前置节点,因此需要一个虚拟的头节点作为哨兵,确保所有节点都有前驱可操作。
3. 为什么选择双向链表而非单向链表?
双向链表不仅保证了线程等待的有序性,更重要的是支持高效的队列操作。当节点取消或发生异常时,双向结构允许从尾节点向前遍历查找有效节点,避免死锁风险,这是 AQS 能够支撑复杂同步逻辑的关键。
4. 新节点是头插还是尾插?唤醒为何有时从后往前找?
新节点肯定是尾插,这符合先进先出的原则。至于唤醒策略,很多人会误以为总是从尾节点开始找,其实不然。正常流程下,当前节点释放锁后会优先检查自己的下一个节点;只有当下一个节点状态异常(如已取消)时,才会触发从尾向前的遍历查找。
这样做主要有两点考量:一是节点取消后置节点可能置为 null,从头遍历遇到 null 容易中断;二是 CAS 修改指针过程中可能出现临时空值。从后往前遍历能确保找到最后一个有效的可用节点,避免死锁。
CountDownLatch 原理剖析
CountDownLatch 是一个闭锁,允许一个或多个线程等待其他线程完成一组操作。其核心实现依赖于 AQS,通过静态内部类 Sync 管理计数状态。
Sync 继承自 AbstractQueuedSynchronizer,构造函数接收初始计数值并直接设置到 AQS 的 state 字段中。state 在这里代表了剩余需要等待的任务数量。
public class CountDownLatch {
private static final class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = 4982264981922014374L;
Sync(int count) {
setState(count);
}
int getCount() {
return getState();
}
protected int tryAcquireShared {
(getState() == ) ? : -;
}
{
(;;) {
getState();
(c == )
;
c - ;
(compareAndSetState(c, nextc))
nextc == ;
}
}
}
Sync sync;
}

