1. AQS队列机制如何避免死锁的核心设计
AbstractQueuedSynchronizer(AQS)作为Java并发包的核心框架,其队列实现通过三个关键机制确保系统不会陷入死锁状态。理解这些机制需要先明确死锁的四个必要条件:互斥条件、请求与保持、不可剥夺和循环等待。AQS通过精妙的设计打破了这些条件。
1.1 双向链表的节点状态管理
AQS维护的CLH队列变体中,每个节点都有waitStatus状态标志:
- CANCELLED(1):表示线程已取消获取锁
- SIGNAL(-1):表示后继节点需要被唤醒
- CONDITION(-2):表示节点在条件队列中等待
- PROPAGATE(-3):用于共享模式传播
关键点在于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;
}
这个方法实现了两个重要特性:
- 自动清理已取消的节点,防止队列被污染
- 确保每个正常节点的前驱节点都处于SIGNAL状态
1.2 获取资源的严格顺序性
AQS通过FIFO队列保证资源获取的顺序性,这是避免循环等待的关键。当线程获取资源失败时,会被包装成Node节点加入队列尾部,这个过程通过CAS保证原子性:
java复制private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode);
Node pred = tail;
if (pred != null) {
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
enq(node); // 完整入队逻辑
return node;
}
队列中的线程只会检查其直接前驱是否为头节点,不会形成循环等待链:
java复制final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
return true;
}
1.3 中断响应与资源释放
AQS对中断的处理也避免了死锁:
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;
// 将节点状态设为CANCELLED
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; // help GC
}
}
2. 关键方法实现细节剖析
2.1 资源获取流程
完整的acquireQueued方法展示了AQS如何管理线程状态:
java复制final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
这个方法实现了:
- 检查前驱节点是否为头节点(保证顺序性)
- 尝试获取资源(tryAcquire由子类实现)
- 设置中断标志但不立即响应(避免死锁情况下丢失中断)
- 最终失败时的清理工作
2.2 资源释放流程
release操作同样考虑了死锁预防:
java复制public final boolean release(int arg) {
if (tryRelease(arg)) { // 子类实现
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h); // 唤醒后继
return true;
}
return false;
}
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 超时与中断处理
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中实现了精确的时限控制:
java复制private boolean doAcquireNanos(int arg, long nanosTimeout)
throws InterruptedException {
if (nanosTimeout <= 0L)
return false;
final long deadline = System.nanoTime() + nanosTimeout;
final Node node = addWaiter(Node.EXCLUSIVE);
boolean failed = true;
try {
for (;;) {
final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return true;
}
nanosTimeout = deadline - System.nanoTime();
if (nanosTimeout <= 0L)
return false;
if (shouldParkAfterFailedAcquire(p, node) &&
nanosTimeout > spinForTimeoutThreshold)
LockSupport.parkNanos(this, nanosTimeout);
if (Thread.interrupted())
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}
3.2 共享模式下的传播机制
共享模式通过PROPAGATE状态保证资源释放能正确传播:
java复制private void setHeadAndPropagate(Node node, int propagate) {
Node h = head; // Record old head for check below
setHead(node);
if (propagate > 0 || h == null || h.waitStatus < 0 ||
(h = head) == null || h.waitStatus < 0) {
Node s = node.next;
if (s == null || s.isShared())
doReleaseShared();
}
}
4. 实际应用中的注意事项
- 锁顺序:虽然AQS内部避免了死锁,但业务代码中多个锁的获取顺序仍需保持一致
- 超时设置:对于可能阻塞的操作,总是建议使用带超时的方法
- 中断处理:正确处理中断状态,避免屏蔽中断导致无法取消
- 资源释放:确保在finally块中释放资源
- 性能监控:对于关键路径上的锁,建议添加监控统计等待时间
java复制// 正确的锁使用模板
Lock lock = new ReentrantLock();
try {
if (lock.tryLock(1, TimeUnit.SECONDS)) {
try {
// 临界区代码
} finally {
lock.unlock();
}
} else {
// 超时处理逻辑
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt(); // 恢复中断状态
// 中断处理逻辑
}
AQS的这种设计使得基于它构建的同步器(如ReentrantLock、Semaphore等)都能继承这些死锁预防特性。理解这些机制对于正确使用并发工具和诊断死锁问题至关重要。
