作为一个在Java并发领域摸爬滚打多年的老码农,我见过太多被AbstractQueuedSynchronizer(AQS)劝退的开发者。这个位于java.util.concurrent.locks包下的抽象类,堪称Java并发编程中的"九转金丹",掌握它就能在并发世界畅通无阻。但枯燥的源码和晦涩的队列操作,总让人望而生畏。
直到某个深夜调试CLH队列时,我突然想到:如果把AQS比作修仙小说中的修炼体系,state变量是灵力值,CLH队列是门派弟子排队的练功房,那一切不就生动起来了?这种跨界类比不仅让理解过程充满趣味,更重要的是建立了形象化的记忆锚点。
在修仙世界中,每个修士体内都运行着灵力循环系统。这正好对应AQS中的volatile int state变量——它就像修士的灵力值,决定着能否获取资源(进入临界区)。当state=0时表示秘境无人占领,state>0时则表示已被N个修士共享(共享锁模式)。
java复制// 修真界的基础灵力模型
public abstract class AbstractQueuedSynchronizer {
private volatile int state; // 灵力值
protected final int getState() {
return state;
}
protected final void setState(int newState) {
state = newState;
}
}
关键理解:volatile保证所有修士(线程)看到的灵力值都是最新的,避免走火入魔(内存可见性问题)
当多个修士争夺同一个秘境时,AQS采用CLH队列管理等待者。这就像门派中的练功房排队系统:
java复制static final class Node {
volatile int waitStatus; // 弟子状态:CANCELED(1)、SIGNAL(-1)...
volatile Node prev; // 前驱弟子
volatile Node next; // 后继弟子
volatile Thread thread; // 弟子本体
Node nextWaiter; // 共享/独占模式标记
}
AQS作为抽象类,定义了修炼的基本框架(排队、唤醒机制),但具体如何获取/释放资源,需要各门派(具体实现类)自行编写功法:
以ReentrantLock的公平锁为例,一次完整的锁获取就像修士挑战秘境:
java复制// 修真版的锁获取流程
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
释放锁的过程如同修士离开秘境:
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;
}
不是所有修士都能成功获取资源,中途可能走火入魔(中断/超时):
实战经验:取消节点的处理是AQS最复杂的部分之一,容易引发内存泄漏和唤醒丢失问题
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) throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
java复制// 信号量的获取实现
protected int tryAcquireShared(int acquires) {
for (;;) {
int available = getState();
int remaining = available - acquires;
if (remaining < 0 ||
compareAndSetState(available, remaining))
return remaining; // 返回剩余许可数
}
}
java复制// 闭锁的实现关键
public void await() throws InterruptedException {
sync.acquireSharedInterruptibly(1); // 共享模式获取
}
protected int tryAcquireShared(int acquires) {
return (getState() == 0) ? 1 : -1; // 检查state是否为0
}
当多个修士互相等待对方的资源时,就会形成死锁。可以用jstack工具查看线程栈:
code复制"Thread-1" #12 prio=5 os_prio=0 tid=0x00007fbb3810a800 nid=0x4a1e waiting for monitor entry [0x00007fbb1e4f6000]
java.lang.Thread.State: BLOCKED (on object monitor)
at com.example.DeadLock$2.run(DeadLock.java:30)
- waiting to lock <0x000000076b1a1c58> (a java.lang.Object)
- locked <0x000000076b1a1c68> (a java.lang.Object)
非公平锁可能导致低优先级线程长期无法获取资源。解决方案:
当park()的线程不被unpark()时,会导致线程永久阻塞。常见原因:
避坑指南:永远在finally中释放锁资源!
java复制ReentrantLock lock = new ReentrantLock();
try {
lock.lock();
// 修炼代码...
} finally {
lock.unlock(); // 确保释放
}
让我们实现一个简单的"双人秘境"同步器,只允许两个修士同时进入:
java复制public class TwinLock implements Lock {
private final Sync sync = new Sync(2);
private static final class Sync extends AbstractQueuedSynchronizer {
Sync(int count) {
setState(count);
}
protected int tryAcquireShared(int reduceCount) {
for (;;) {
int current = getState();
int newCount = current - reduceCount;
if (newCount < 0 || compareAndSetState(current, newCount)) {
return newCount;
}
}
}
protected boolean tryReleaseShared(int returnCount) {
for (;;) {
int current = getState();
int newCount = current + returnCount;
if (compareAndSetState(current, newCount)) {
return true;
}
}
}
}
// 实现Lock接口的其他方法...
}
这个实现展示了AQS的核心价值:通过共享模式state的管理,我们只需关注资源获取/释放的逻辑,排队、唤醒等复杂操作都由AQS框架完成。
在acquireQueued方法中,AQS会先自旋尝试获取锁,失败后才park:
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);
}
}
调优建议:在低竞争场景下,可以增加自旋次数减少上下文切换
AQS的Node节点通过CAS操作修改状态,可以考虑缓存行填充:
java复制@sun.misc.Contended
static final class Node {
// 原有字段...
}
这个注解可以避免伪共享(False Sharing)问题,在Java 8+中可用。
使用JFR(Java Flight Recorder)监控锁竞争:
bash复制jcmd <pid> JFR.start duration=60s filename=lock.jfr
jcmd <pid> JFR.dump filename=lock.jfr
分析结果中的"Java Monitor Blocked"事件可以定位热点锁。
AQS的设计体现了Java并发包的几个核心思想:
这种设计思路也体现在其他并发工具中,如ConcurrentHashMap的分段锁、ForkJoinPool的工作窃取等。理解AQS就像掌握了并发编程的内功心法,面对其他高阶并发工具时也能触类旁通。