AQS 同步器——Java 并发框架的核心底座全解析

张开发
2026/5/23 16:08:01 15 分钟阅读
AQS 同步器——Java 并发框架的核心底座全解析
AQS 同步器——Java 并发框架的核心底座全解析在 Java 并发包java.util.concurrent(JUC) 中ReentrantLock、Semaphore、CountDownLatch等工具几乎统治了多线程同步的半壁江山。而这些强大工具的背后都站着同一个“幕后英雄”AQS (AbstractQueuedSynchronizer)。理解 AQS就等于拿到了开启 Java 高级并发编程大门的钥匙。今天我们就剥开这一层层复杂的包装一步步讲透它的核心原理。1. 这篇文章要解决什么问题如果你要设计一个高性能的锁你需要考虑哪些问题状态管理如何记录锁是被占用了还是空闲着原子竞争多个线程同时抢锁如何确保只有一个能成功线程排队没抢到锁的线程该安置在哪里如何让他们有序地等待唤醒机制持锁线程释放后该通知哪一个等待线程来接手AQS 的出现就是为了将这些“脏活累活”统一封装起来。它提供了一套通用的同步管理框架让开发者只需关注业务本身的“获取/释放”逻辑而无需操心复杂的队列管理和线程调度。2. 核心原理AQS 的三大基石AQS 能够高效运转全靠这三个核心组件核心一volatile state (同步状态位)AQS 内部维护了一个名为state的整型变量。语义通常 0 代表资源空闲1 代表资源被占用。对于可重入锁state的数值代表线程重入的次数。可见性通过volatile修饰确保多线程下状态的实时可见。核心二CAS (原子的变更)当多个线程尝试修改state时AQS 使用Unsafe类提供的CAS (Compare-And-Swap)操作。只有 CAS 成功才代表线程成功抢占到了资源。这是一种无锁的乐观策略极大减少了传统重量级锁的开销。核心三CLH 变体队列 (虚拟双向队列)这是 AQS 最复杂的部分。它将没抢到锁的线程封装成一个Node节点并放入一个双向链表中。虚拟性队列中并不真的存储线程对象而是通过 Node 中的指针维持一个“等待链”。头结点 (Head)始终代表当前持有锁的线程。3. 流程/机制描述独占锁的获取全流程我们以acquire(int arg)方法为例看看 AQS 是如何一步步处理抢锁逻辑的。第一步尝试获取 (TryAcquire)AQS 使用模板方法模式。它并不实现具体的获取逻辑而是调用子类实现的tryAcquire。子类通常在这里判断state 0并尝试 CAS 变更。第二步入队 (AddWaiter)如果tryAcquire失败当前线程会被封装成一个Node共享或独占模式并通过 CAS 挂载到等待队列的尾部Tail。第三步自旋与阻塞 (AcquireQueued)入队后的线程并不会立即睡觉它会检查“我的前驱节点是否是 Head”如果是说明我排第二有资格再次尝试抢锁因为 Head 可能随时释放。如果抢到了我就变成新的 Head。如果抢不到或者前驱不是 Head线程会通过LockSupport.park()挂起进入阻塞状态等待被唤醒。4. 常见误区误区 1AQS 队列里存的是线程本身辨析队列存的是Node对象它包含指向线程的引用、等待状态waitStatus以及前后驱指针。这使得 AQS 可以更灵活地管理取消、超时等复杂场景。误区 2只要用了 AQS 性能就一定高辨析AQS 的性能很大程度上取决于子类tryAcquire是否高效以及在高竞争下的唤醒/挂起频率。此外独占模式下的“自旋”次数也是影响性能的关键因素。5. 实际工作中怎么用理解组件基座当你查看ReentrantLock源码时你会发现它内部只有两个子类FairSync和NonfairSync继承了 AQS。掌握了 AQS你就秒懂了公平锁与非公平锁的差异仅仅在于tryAcquire的入队检查。复杂同步场景模拟某些极端场景下如限制只有 3 个线程能同时写 A 业务1 个能读 B 业务现有的并发工具可能不够灵活你可以尝试基于 AQS 定制同步器。监控与诊断在排查多线程死锁或性能瓶颈时通过检查 AQS 同步器的getQueueLength()或hasQueuedThreads()可以清晰判断压力是否堆积在排队环节。总结AQS 是面向对象设计中“解耦”和“复用”的巅峰之作。它将复杂的线程调度抽象为简单的状态管理为 Java 构建了极其稳固的并发地基。作为中高级开发者唯有透彻理解这个“底座”才能在并发编程的海洋中游刃有余。

更多文章