深入剖析Java并发基石:AQS原理与实战
深入剖析Java并发基石:AQS原理与实战
一、为什么需要AQS?
在Java并发编程中,线程同步是保证数据一致性的核心挑战。JDK 1.5之前仅依赖synchronized
关键字,存在灵活性差、功能单一等问题。Doug Lea设计的AQS框架,为ReentrantLock
、Semaphore
等同步组件提供了统一底层支持,成为Java并发包的基石。
二、AQS核心设计思想
1. 核心结构
-
state
状态变量:
32位整型值,通过volatile
保证可见性。不同组件中含义不同:ReentrantLock
:表示重入次数Semaphore
:表示可用许可证数量CountDownLatch
:表示未完成的计数
-
CLH队列(FIFO双向链表):
存储等待线程的队列,节点类型为Node
,包含:volatile int waitStatus; // 节点状态(CANCELLED/SIGNAL/CONDITION等) volatile Node prev; // 前驱节点 volatile Node next; // 后继节点 Thread thread; // 关联线程
2. 模板方法模式
AQS采用模板方法模式,将核心逻辑封装在acquire()
、release()
等方法中,子类只需实现:
tryAcquire(int arg)
:尝试获取资源(独占模式)tryRelease(int arg)
:尝试释放资源tryAcquireShared(int arg)
:共享模式获取tryReleaseShared(int arg)
:共享模式释放
三、两种同步模式详解
1. 独占模式(Exclusive)
以ReentrantLock
为例:
-
加锁流程:
- 线程调用
lock()
时,尝试通过tryAcquire()
修改state
(0→1)。 - 若失败,将线程封装为
Node.SIGNAL
节点加入队列尾部,通过LockSupport.park()
阻塞。 - 当头部节点的后继节点被唤醒时,重新尝试获取锁。
- 线程调用
-
解锁流程:
- 调用
unlock()
执行tryRelease()
减少state
。 - 若
state=0
,唤醒后继节点中的线程。
- 调用
重入性实现:
state
记录重入次数,释放时需匹配调用次数。
2. 共享模式(Shared)
以Semaphore
为例:
-
获取许可证:
acquire()
调用tryAcquireShared()
减少state
(剩余许可证)。- 若
state<0
,线程入队阻塞。 - 前驱节点释放时,唤醒连续多个共享节点(与独占模式区别)。
-
释放许可证:
release()
增加state
,唤醒后续节点。- 唤醒传播直至无资源可用。
四、关键源码流程剖析
1. acquire()
方法流程(独占模式)
public final void acquire(int arg) {if (!tryAcquire(arg) && // 子类实现获取逻辑acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) // 加入队列selfInterrupt();
}
addWaiter()
:将线程包装为Node
并插入队尾(CAS保证线程安全)。acquireQueued()
:- 循环检查当前节点是否为头节点的后继(即“第二顺位”)。
- 若符合条件则尝试
tryAcquire()
,成功后将自己设为新头节点。 - 否则调用
LockSupport.park()
挂起线程。
2. 节点唤醒机制
- 当头部节点释放锁时:
- 调用
unparkSuccessor()
找到离头节点最近的非取消状态节点。 - 通过
LockSupport.unpark(node.thread)
唤醒线程。
- 调用
五、AQS在同步组件中的应用
组件 | 同步模式 | state 含义 | 特性 |
---|---|---|---|
ReentrantLock | 独占 | 重入次数 | 支持公平/非公平锁 |
Semaphore | 共享 | 可用许可证数量 | 可控制并发线程数 |
CountDownLatch | 共享 | 未完成的任务数 | 一次性屏障,不可重置 |
ReentrantReadWriteLock | 组合模式 | 高16位读锁,低16位写锁 | 读写分离,写锁优先 |
六、AQS的进阶特性
-
可中断与超时:
acquireInterruptibly()
和tryAcquireNanos()
支持响应中断和超时控制。 -
条件变量(Condition):
通过ConditionObject
实现等待队列,与同步队列分离。await()
:释放锁并加入条件队列。signal()
:将条件队列节点转移到同步队列。
-
公平 vs 非公平锁:
- 公平锁:严格按队列顺序获取资源。
- 非公平锁:新线程直接尝试插队(性能更高,可能引发饥饿)。
七、AQS的设计哲学与局限
优势:
- 解耦性:分离同步状态管理与线程排队逻辑。
- 扩展性:基于模板方法支持多样化同步组件。
- 性能优化:自旋+CAS减少上下文切换。
注意事项:
- 子类需重写
try*
方法时保证线程安全(通常用CAS操作)。 - 避免在
tryAcquire()
中调用可能阻塞的操作。
适用场景:高并发锁、资源池管理、任务协调等。不适用于IO密集型阻塞操作。
总结
AQS通过state
状态和CLH队列的协作,为Java并发提供了高效、灵活的底层支持。理解其工作原理,不仅能优化高并发程序设计,更是深入Java并发体系的必经之路。其设计思想(如模板方法、CAS自旋)在分布式锁等场景中亦有广泛应用。