【JUC】AQS锁
AQS(AbstractQueuedSynchronizer)深入解析
AQS的核心设计
AQS作为Java并发包的基石,采用模板方法设计模式,为构建锁和同步器提供了通用框架。其核心由三部分组成:
- volatile修饰的state变量:作为同步状态标识,不同同步器赋予其不同含义
- FIFO双向队列(CLH变体):用于管理等待线程的队列结构
- 需要重写的模板方法:如tryAcquire、tryRelease等,由子类实现具体同步逻辑
同步状态state的多样化含义
state变量虽然只是一个int值,但在不同同步器中被赋予了丰富的语义:
- Semaphore(信号量):state表示可用许可证的数量
- CountDownLatch(倒计时锁):state表示还需要倒数的数量
- ReentrantLock(可重入锁):state表示锁的占有情况,0表示空闲,大于0表示被占有次数(支持重入)
- ReentrantReadWriteLock(读写锁):巧妙利用int的32位,高16位记录读锁持有数,低16位记录写锁重入次数
由于state被volatile修饰,保证了对所有线程的可见性。但其并发修改需要线程安全保证,AQS通过sun.misc.Unsafe类提供的CAS(Compare-And-Swap)操作来实现原子性更新,主要方法包括getState()、setState()和compareAndSetState()。
AQS的双向队列机制
AQS维护的等待队列是一个CLH(Craig, Landin, and Hagersten)锁队列的变体。每个等待线程被封装为一个Node节点,节点中包含了线程引用、等待状态等信息。队列采用FIFO原则,但实现了更灵活的唤醒机制。
当线程尝试获取同步状态失败时,它会被构造成Node节点加入队列尾部。节点在队列中会自旋检查前驱节点是否为头节点,如果是则尝试获取同步状态;否则可能会被挂起等待唤醒。这种设计减少了线程间的竞争,提高了并发效率。
AQS的两种同步模式
AQS支持两种同步模式:
- 独占模式(Exclusive):同一时刻只有一个线程能获取同步状态,如ReentrantLock
- 共享模式(Shared):多个线程可以同时获取同步状态,如Semaphore、CountDownLatch
两种模式对应不同的模板方法,子类可根据需要实现其中一种或同时支持两种模式。
公平性与非公平性实现
基于AQS构建的锁可以灵活实现公平性和非公平性策略:
- 公平锁:严格按照队列FIFO顺序获取锁,新请求的线程会先检查队列中是否有等待线程
- 非公平锁:允许插队,新请求的线程可以直接尝试获取锁,不管队列中是否有等待线程
公平性通过hasQueuedPredecessors()方法判断队列中是否有更早等待的线程来实现。非公平锁虽然可能造成线程饥饿,但通常能提供更高的吞吐量。
Condition条件队列
除了同步队列,AQS还通过内部类ConditionObject实现了条件等待队列机制。每个Condition对象维护一个独立的条件队列,支持await()、signal()等操作,实现了类似Object.wait()/notify()但更强大的等待/通知机制。与synchronized内置锁相比,AQS的条件等待支持多个条件队列、可中断等待、超时等待等高级特性。
用AQS实现自定义锁
要使用AQS实现自定义锁,通常遵循以下步骤:
- 定义继承AbstractQueuedSynchronizer的内部类Sync
- 根据需求选择独占或共享模式,重写对应方法
- 使用CAS操作安全修改state状态
- 如果是独占锁,还需要维护当前持有锁的线程
- 对外提供锁的API,委托给Sync实例
例如,实现一个基本的互斥锁需要重写tryAcquire()和tryRelease()方法,通过CAS将state从0改为1表示获取锁,从1改为0表示释放锁。对于可重入锁,还需要记录重入次数和当前持有线程。
AQS的性能优势
AQS之所以成为Java并发包的核心,主要得益于以下设计优势:
- 减少竞争:通过队列化减少不必要的CAS竞争
- 避免惊群效应:精确唤醒后继节点而非所有等待线程
- 支持中断和超时:提供了完善的线程协作机制
- 模板方法模式:将通用逻辑封装,子类只需关注状态管理
- 可扩展性:基于AQS可以构建各种复杂的同步器
总结
AQS通过将同步状态的管理与线程排队等待机制分离,提供了一个高度灵活和高效的同步框架。其设计充分考虑了多核处理器环境下的性能特点,既保证了正确性又兼顾了效率。理解AQS不仅有助于正确使用Java并发工具,也为设计自定义同步组件提供了坚实基础。
在实际开发中,大部分同步需求都可以通过Java并发包中基于AQS实现的类来满足。只有在特殊场景下,才需要考虑基于AQS实现自定义同步器,这时需要深入理解AQS的工作原理,特别是状态管理、队列操作和CAS使用的细节。