末日逃亡(僵尸射击
240.4MB · 2026-04-01
在 Java 并发包 java.util.concurrent (JUC) 中,ReentrantLock、Semaphore、CountDownLatch 等工具几乎统治了多线程同步的半壁江山。而这些强大工具的背后,都站着同一个“幕后英雄”:AQS (AbstractQueuedSynchronizer)。
理解 AQS,就等于拿到了开启 Java 高级并发编程大门的钥匙。今天,我们就剥开这一层层复杂的包装,一步步讲透它的核心原理。
如果你要设计一个高性能的锁,你需要考虑哪些问题?
AQS 的出现,就是为了将这些“脏活累活”统一封装起来。它提供了一套通用的同步管理框架,让开发者只需关注业务本身的“获取/释放”逻辑,而无需操心复杂的队列管理和线程调度。
AQS 能够高效运转,全靠这三个核心组件:
AQS 内部维护了一个名为 state 的整型变量。
state 的数值代表线程重入的次数。volatile 修饰,确保多线程下状态的实时可见。当多个线程尝试修改 state 时,AQS 使用 Unsafe 类提供的 CAS (Compare-And-Swap) 操作。只有 CAS 成功,才代表线程成功抢占到了资源。这是一种无锁的乐观策略,极大减少了传统重量级锁的开销。
这是 AQS 最复杂的部分。它将没抢到锁的线程封装成一个 Node 节点,并放入一个双向链表中。
我们以 acquire(int arg) 方法为例,看看 AQS 是如何一步步处理抢锁逻辑的。
AQS 使用 模板方法模式。它并不实现具体的获取逻辑,而是调用子类实现的 tryAcquire。子类通常在这里判断 state == 0 并尝试 CAS 变更。
如果 tryAcquire 失败,当前线程会被封装成一个 Node(共享或独占模式),并通过 CAS 挂载到等待队列的尾部(Tail)。
入队后的线程并不会立即睡觉,它会检查:“我的前驱节点是否是 Head?”
LockSupport.park() 挂起,进入阻塞状态,等待被唤醒。辨析:队列存的是 Node 对象,它包含指向线程的引用、等待状态 waitStatus 以及前后驱指针。这使得 AQS 可以更灵活地管理取消、超时等复杂场景。
辨析:AQS 的性能很大程度上取决于子类 tryAcquire 是否高效,以及在高竞争下的唤醒/挂起频率。此外,独占模式下的“自旋”次数也是影响性能的关键因素。
ReentrantLock 源码时,你会发现它内部只有两个子类 FairSync 和 NonfairSync 继承了 AQS。掌握了 AQS,你就秒懂了公平锁与非公平锁的差异仅仅在于 tryAcquire 的入队检查。getQueueLength() 或 hasQueuedThreads(),可以清晰判断压力是否堆积在排队环节。AQS 是面向对象设计中“解耦”和“复用”的巅峰之作。它将复杂的线程调度抽象为简单的状态管理,为 Java 构建了极其稳固的并发地基。作为中高级开发者,唯有透彻理解这个“底座”,才能在并发编程的海洋中游刃有余。