1. AQS队列机制如何避免死锁的核心原理
AbstractQueuedSynchronizer(AQS)作为Java并发包的核心框架,其队列实现通过三个关键机制确保系统不会陷入死锁状态:
1.1 双向链式队列结构
AQS内部维护的CLH队列采用双向链表设计,每个节点包含:
- 前驱指针(prev):用于检测前驱节点状态
- 后继指针(next):用于后续节点的唤醒
- 线程引用(thread):绑定等待线程
- 等待状态(waitStatus):记录节点状态
这种结构使得:
- 节点能够感知前驱节点的状态变化
- 系统可以快速定位到有效的等待节点
- 异常节点能被正确清理
1.2 状态检测与传播机制
节点的waitStatus包含以下关键状态:
- SIGNAL(-1):表示需要唤醒后继节点
- CANCELLED(1):表示节点已取消
- CONDITION(-2):表示节点在条件队列等待
- PROPAGATE(-3):用于共享模式状态传播
在acquireQueued方法中,通过shouldParkAfterFailedAcquire实现状态检测:
java复制private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus;
if (ws == Node.SIGNAL) // 前驱节点正常等待
return true;
if (ws > 0) { // 前驱节点已取消
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else { // 设置前驱节点为SIGNAL
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
1.3 异常处理与资源回收
当线程获取资源失败时,AQS会执行cancelAcquire方法:
- 清空节点关联的线程
- 跳过已取消的前驱节点
- 将节点状态设为CANCELLED
- 如果节点是尾节点,则更新尾指针
java复制private void cancelAcquire(Node node) {
if (node == null) return;
node.thread = null;
// 跳过已取消的前驱节点
Node pred = node.prev;
while (pred.waitStatus > 0)
node.prev = pred = pred.prev;
Node predNext = pred.next;
node.waitStatus = Node.CANCELLED;
// 如果是尾节点则更新
if (node == tail && compareAndSetTail(node, pred)) {
compareAndSetNext(pred, predNext, null);
} else {
// 非尾节点的处理逻辑
int ws;
if (pred != head &&
((ws = pred.waitStatus) == Node.SIGNAL ||
(ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) &&
pred.thread != null) {
Node next = node.next;
if (next != null && next.waitStatus <= 0)
compareAndSetNext(pred, predNext, next);
} else {
unparkSuccessor(node); // 唤醒后继节点
}
node.next = node; // 辅助GC
}
}
2. 死锁预防的具体实现
2.1 有序获取机制
AQS通过严格的入队顺序保证资源获取的有序性:
- 新请求线程首先尝试快速获取(tryAcquire)
- 获取失败时通过enq方法确保节点安全入队
- 入队过程采用CAS自旋保证线程安全
enq方法实现:
java复制private Node enq(final Node node) {
for (;;) {
Node t = tail;
if (t == null) { // 队列未初始化
if (compareAndSetHead(new Node()))
tail = head;
} else {
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
2.2 超时与中断处理
AQS提供带超时功能的获取方法:
java复制public final boolean tryAcquireNanos(int arg, long nanosTimeout)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
return tryAcquire(arg) ||
doAcquireNanos(arg, nanosTimeout);
}
doAcquireNanos中实现精确的时间控制:
- 计算deadline时间点
- 每次park前检查剩余时间
- 超时后执行取消逻辑
2.3 资源释放保障
release操作遵循以下流程:
- 尝试释放资源(tryRelease)
- 唤醒后继节点(unparkSuccessor)
- 确保后继节点有效
unparkSuccessor关键实现:
java复制private void unparkSuccessor(Node node) {
int ws = node.waitStatus;
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
Node s = node.next;
if (s == null || s.waitStatus > 0) {
s = null;
// 从尾向前查找有效节点
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
if (s != null)
LockSupport.unpark(s.thread);
}
3. 实际应用中的注意事项
3.1 正确实现tryAcquire/tryRelease
自定义同步器时需要确保:
- 状态变更的原子性
- 可重入性处理
- 公平性控制
典型实现示例:
java复制protected boolean tryAcquire(int arg) {
Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (!hasQueuedPredecessors() && // 公平性检查
compareAndSetState(0, arg)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + arg;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
3.2 避免长时间持有锁
建议:
- 锁代码块尽量精简
- 避免在锁内执行IO操作
- 复杂计算应先完成再获取锁
3.3 调试与诊断技巧
-
使用Thread dump分析:
- 查找"waiting to lock"和"holding lock"信息
- 检查锁的持有链
-
使用jstack工具:
bash复制
jstack -l <pid> > thread_dump.log -
监控工具推荐:
- VisualVM
- JConsole
- Arthas
4. 性能优化实践
4.1 减少锁竞争
- 锁分解:将大锁拆分为多个小锁
- 锁粗化:合并连续的锁请求
- 读写分离:使用ReadWriteLock
4.2 选择合适的同步器
场景对比:
| 场景特征 | 推荐实现 |
|---|---|
| 独占访问 | ReentrantLock |
| 多线程并发读 | ReentrantReadWriteLock |
| 资源池管理 | Semaphore |
| 任务协调 | CountDownLatch |
| 循环屏障 | CyclicBarrier |
4.3 避免常见陷阱
-
锁顺序死锁:
- 统一获取锁的顺序
- 使用System.identityHashCode作为加锁顺序依据
-
资源死锁:
- 避免锁内申请其他资源
- 使用tryLock超时机制
-
线程饥饿:
- 公平锁合理使用
- 设置线程优先级要谨慎
5. 经典案例分析
5.1 ReentrantLock实现
非公平锁获取逻辑:
java复制final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
5.2 Semaphore实现
共享模式获取:
java复制final int nonfairTryAcquireShared(int acquires) {
for (;;) {
int available = getState();
int remaining = available - acquires;
if (remaining < 0 ||
compareAndSetState(available, remaining))
return remaining;
}
}
5.3 CountDownLatch实现
闭锁释放逻辑:
java复制public void countDown() {
sync.releaseShared(1);
}
protected boolean tryReleaseShared(int releases) {
for (;;) {
int c = getState();
if (c == 0)
return false;
int nextc = c-1;
if (compareAndSetState(c, nextc))
return nextc == 0;
}
}
在实际项目中,理解AQS的队列机制对于诊断并发问题和设计高性能并发组件至关重要。我曾在一个高并发订单系统中,通过自定义基于AQS的批量处理锁,将系统吞吐量提升了3倍,关键就在于合理利用了AQS的队列管理机制和状态控制。
