1. 理解AQS的本质
第一次接触AQS时,我盯着这个缩写看了半天——AbstractQueuedSynchronizer,抽象队列同步器。名字听起来就很唬人,但当我真正理解它的设计哲学后,才发现这简直是Java并发编程中的瑞士军刀。AQS本质上是一个构建锁和同步器的框架,它用了一个int成员变量表示同步状态,通过内置的FIFO队列来完成资源获取线程的排队工作。
重要提示:AQS不是直接给开发者用的工具类,而是给那些需要实现自己同步原语的大神们准备的底层框架。我们日常用的ReentrantLock、CountDownLatch等其实都是基于AQS实现的。
AQS最精妙的地方在于它采用了模板方法模式。它定义了acquire和release这样的模板方法,但把具体的资源获取和释放逻辑留给子类实现。这种设计让AQS既能保持核心排队机制的稳定性,又能灵活支持各种同步场景。
2. AQS的核心机制解析
2.1 同步状态与CAS操作
AQS内部维护了一个volatile int类型的state变量,这个变量就是同步状态的核心。不同的同步器对state的解释不同:
- 在ReentrantLock中,state表示锁的持有计数
- 在Semaphore中,state表示可用许可数
- 在CountDownLatch中,state表示计数器值
所有对state的修改都通过CAS(Compare-And-Swap)操作完成,这是AQS能够实现无锁并发的基础。在JDK中,这个操作通过Unsafe类实现:
protected final boolean compareAndSetState(int expect, int update) { return unsafe.compareAndSwapInt(this, stateOffset, expect, update); }2.2 等待队列的实现
当线程获取资源失败时,AQS会将线程封装成一个Node节点加入CLH队列(一种改进的CLH锁队列)。这个队列是AQS能够实现公平性的关键:
static final class Node { volatile int waitStatus; volatile Node prev; volatile Node next; volatile Thread thread; Node nextWaiter; // ... }每个Node节点保存了线程引用、前后节点指针以及等待状态。队列的入队和出队操作同样通过CAS保证线程安全。
2.3 独占与共享模式
AQS支持两种同步模式:
- 独占模式(Exclusive):同一时刻只有一个线程能获取资源,如ReentrantLock
- 共享模式(Shared):多个线程可以同时获取资源,如Semaphore
这两种模式通过不同的acquire/release方法实现:
- acquire/release:独占模式
- acquireShared/releaseShared:共享模式
3. 基于AQS实现自定义同步器
3.1 实现一个简单的互斥锁
让我们通过实现一个简单的不可重入互斥锁来理解AQS的使用:
public class MyMutex { private static class Sync extends AbstractQueuedSynchronizer { @Override protected boolean tryAcquire(int arg) { return compareAndSetState(0, 1); } @Override protected boolean tryRelease(int arg) { setState(0); return true; } @Override protected boolean isHeldExclusively() { return getState() == 1; } } private final Sync sync = new Sync(); public void lock() { sync.acquire(1); } public void unlock() { sync.release(1); } }这个实现虽然简单,但包含了AQS使用的核心要点:
- 继承AQS并实现tryAcquire/tryRelease方法
- 使用state表示锁状态(0表示未锁定,1表示锁定)
- 通过acquire/release方法提供外部接口
3.2 实现一个简单的CountDownLatch
再来看看如何实现一个简单的CountDownLatch:
public class MyCountDownLatch { private static class Sync extends AbstractQueuedSynchronizer { Sync(int count) { setState(count); } @Override protected int tryAcquireShared(int acquires) { return (getState() == 0) ? 1 : -1; } @Override 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; } } } private final Sync sync; public MyCountDownLatch(int count) { this.sync = new Sync(count); } public void await() throws InterruptedException { sync.acquireSharedInterruptibly(1); } public void countDown() { sync.releaseShared(1); } }这个实现展示了AQS共享模式的使用,关键点在于:
- tryAcquireShared返回负值表示获取失败
- tryReleaseShared通过CAS循环减少计数器
- 当计数器归零时,所有等待线程会被唤醒
4. AQS在JDK中的应用实例
4.1 ReentrantLock的实现
ReentrantLock是AQS最经典的用法之一。它的内部类Sync继承自AQS,并实现了可重入特性:
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; }这段代码展示了可重入锁的关键逻辑:
- 如果锁未被持有(state=0),尝试获取
- 如果锁已被当前线程持有,增加重入计数
- 释放时相应减少计数,归零时完全释放
4.2 Semaphore的实现
Semaphore使用AQS的共享模式来控制资源访问:
protected int tryAcquireShared(int acquires) { for (;;) { int available = getState(); int remaining = available - acquires; if (remaining < 0 || compareAndSetState(available, remaining)) return remaining; } }Semaphore的tryAcquireShared实现展示了:
- 计算剩余许可数
- 如果不足直接返回负值
- 否则尝试CAS更新状态
5. AQS性能优化技巧
5.1 减少CAS竞争
在高并发场景下,AQS的CAS操作可能成为瓶颈。我们可以通过以下方式优化:
- 使用自旋等待:在入队前短暂自旋,可能避免队列操作
- 减少state修改频率:比如在读写锁中,使用位运算分离读写状态
- 使用ThreadLocal缓存:对于可重入锁,可以缓存部分状态
5.2 队列优化
AQS的CLH队列可以针对特定场景优化:
- 对于短时任务,可以设置自旋超时
- 对于读多写少的场景,可以偏向读锁
- 使用条件队列时,注意避免虚假唤醒
6. AQS常见问题排查
6.1 死锁问题
虽然AQS本身不会导致死锁,但使用不当仍然可能发生:
// 错误示例:嵌套锁可能导致死锁 public void transfer(MyMutex lock1, MyMutex lock2) { lock1.lock(); lock2.lock(); try { // 转账操作 } finally { lock2.unlock(); lock1.unlock(); } }解决方案:
- 按固定顺序获取锁
- 使用tryLock设置超时
- 使用更高级别的并发工具
6.2 性能问题
AQS在高竞争场景下可能出现性能问题,表现为:
- CAS操作失败率高
- 队列过长导致上下文切换频繁
解决方法:
- 减小锁粒度
- 使用读写锁分离读写操作
- 考虑无锁算法替代方案
7. AQS最佳实践
7.1 正确实现tryAcquire
实现tryAcquire时需要注意:
- 必须保证线程安全
- 考虑可重入性需求
- 正确处理中断
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; }7.2 合理使用条件队列
AQS的条件队列(ConditionObject)提供了更灵活的等待/通知机制:
public class BoundedBuffer { private final Lock lock = new ReentrantLock(); private final Condition notFull = lock.newCondition(); private final Condition notEmpty = lock.newCondition(); public void put(Object x) throws InterruptedException { lock.lock(); try { while (count == items.length) notFull.await(); items[putPtr] = x; if (++putPtr == items.length) putPtr = 0; ++count; notEmpty.signal(); } finally { lock.unlock(); } } // 类似的take方法... }使用条件队列时要注意:
- 总是在循环中检查条件
- 先获取锁再调用await/signal
- 使用多个Condition分离不同的等待条件
8. AQS源码深度解析
8.1 acquireQueued方法剖析
这是AQS最核心的方法之一,处理排队线程的阻塞和唤醒:
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); } }这个方法展示了:
- 只有前驱是头节点时才尝试获取(公平性)
- 获取失败后检查是否需要阻塞
- 处理中断标志
- 最终处理失败情况
8.2 取消机制分析
当线程在等待过程中被中断或超时,需要取消获取操作:
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; // help GC } }取消操作需要考虑:
- 清理线程引用
- 跳过已取消的前驱
- 处理不同位置的节点
- 必要时唤醒后继
9. AQS扩展与变种
9.1 响应中断的获取操作
AQS提供了多种获取方法应对不同需求:
- acquire:不响应中断
- acquireInterruptibly:响应中断
- tryAcquireNanos:带超时和中断响应
public final void acquireInterruptibly(int arg) throws InterruptedException { if (Thread.interrupted()) throw new InterruptedException(); if (!tryAcquire(arg)) doAcquireInterruptibly(arg); }9.2 自定义同步器示例:限流器
基于AQS实现一个简单的令牌桶限流器:
public class RateLimiter { private final Sync sync; private final Timer timer; private static final class Sync extends AbstractQueuedSynchronizer { private final int maxPermits; private long nextFreeTicket; Sync(int permits) { maxPermits = permits; setState(permits); nextFreeTicket = System.nanoTime(); } @Override protected int tryAcquireShared(int acquires) { for (;;) { long now = System.nanoTime(); long next = nextFreeTicket; long wait = Math.max(next - now, 0); if (wait > 0) { return -1; } int available = getState(); int remaining = available - acquires; if (remaining < 0 || compareAndSetState(available, remaining)) { nextFreeTicket = now + 1_000_000_000L / maxPermits; return remaining; } } } @Override protected boolean tryReleaseShared(int releases) { for (;;) { int current = getState(); int next = current + releases; if (next > maxPermits) { next = maxPermits; } if (compareAndSetState(current, next)) { return true; } } } } public RateLimiter(int permits) { this.sync = new Sync(permits); this.timer = new Timer(true); } public void acquire() throws InterruptedException { sync.acquireSharedInterruptibly(1); } public void release() { sync.releaseShared(1); } }这个实现展示了:
- 令牌桶算法原理
- 精确的时间控制
- AQS共享模式的高级用法
10. AQS与现代并发编程
随着Java并发API的发展,AQS仍然是许多高级并发工具的基础:
- StampedLock:结合了AQS和乐观读
- Phaser:更灵活的同步屏障
- CompletableFuture:部分实现依赖AQS
理解AQS有助于我们:
- 更高效地使用标准库并发工具
- 在必要时实现自定义同步器
- 诊断复杂的并发问题
在实际项目中,我经常遇到需要基于AQS实现特殊同步需求的场景。比如最近我们实现了一个分布式任务调度系统,其中的本地任务队列就使用了自定义的AQS同步器来协调多个工作线程。通过合理设计tryAcquire/tryRelease逻辑,我们实现了比标准锁更高效的并发控制。