文章出处 http://www.cnblogs.com/micrari/p/6937995.html
1. 背景
AQS(java.util.concurrent.locks.AbstractQueuedSynchronizer)是Doug Lea大师创作的用来构建锁或者其他同步组件(信号量、事件等)的基础框架类。JDK中许多并发工具类的内部实现都依赖于AQS,如ReentrantLock, Semaphore, CountDownLatch等等。学习AQS的使用与源码实现对深入理解concurrent包中的类有很大的帮助。
本文重点介绍AQS中的基本实现思路,包括独占锁、共享锁的获取和释放实现原理和一些代码细节。
对于AQS中ConditionObject的相关实现,可以参考我的另一篇博文AbstractQueuedSynchronizer源码解读--续篇之Condition。
2. 简介
AQS的主要使用方式是继承它作为一个内部辅助类实现同步原语,它可以简化你的并发工具的内部实现,屏蔽同步状态管理、线程的排队、等待与唤醒等底层操作。
AQS设计基于模板方法模式,开发者需要继承同步器并且重写指定的方法,将其组合在并发组件的实现中,调用同步器的模板方法,模板方法会调用使用者重写的方法。
3. 实现思路
下面介绍下AQS具体实现的大致思路。
AQS内部维护一个CLH队列来管理锁。
线程会首先尝试获取锁,如果失败,则将当前线程以及等待状态等信息包成一个Node节点加到同步队列里。
接着会不断循环尝试获取锁(条件是当前节点为head的直接后继才会尝试),如果失败则会阻塞自己,直至被唤醒;
而当持有锁的线程释放锁时,会唤醒队列中的后继线程。
下面列举JDK中几种常见使用了AQS的同步组件:
- ReentrantLock: 使用了AQS的独占获取和释放,用state变量记录某个线程获取独占锁的次数,获取锁时+1,释放锁时-1,在获取时会校验线程是否可以获取锁。
- Semaphore: 使用了AQS的共享获取和释放,用state变量作为计数器,只有在大于0时允许线程进入。获取锁时-1,释放锁时+1。
- CountDownLatch: 使用了AQS的共享获取和释放,用state变量作为计数器,在初始化时指定。只要state还大于0,获取共享锁会因为失败而阻塞,直到计数器的值为0时,共享锁才允许获取,所有等待线程会被逐一唤醒。
3.1 如何获取锁
获取锁的思路很直接:
while (不满足获取锁的条件) {
把当前线程包装成节点插入同步队列
if (需要阻塞当前线程)
阻塞当前线程直至被唤醒
}
将当前线程从同步队列中移除
以上是一个很简单的获取锁的伪代码流程,AQS的具体实现比这个复杂一些,也稍有不同,但思想上是与上述伪代码契合的。
通过循环检测是否能够获取到锁,如果不满足,则可能会被阻塞,直至被唤醒。
3.2 如何释放锁
释放锁的过程设计修改同步状态,以及唤醒后继等待线程:
修改同步状态
if (修改后的状态允许其他线程获取到锁)
唤醒后继线程
这只是很简略的释放锁的伪代码示意,AQS具体实现中能看到这个简单的流程模型。
3.3 API简介
通过上面的AQS大体思路分析,我们可以看到,AQS主要做了三件事情
- 同步状态的管理
- 线程的阻塞和唤醒
- 同步队列的维护
下面三个protected final方法是AQS中用来访问/修改同步状态的方法:
-
int getState(): 获取同步状态
-
void setState(): 设置同步状态
-
boolean compareAndSetState(int expect, int update):基于CAS,原子设置当前状态
在自定义基于AQS的同步工具时,我们可以选择覆盖实现以下几个方法来实现同步状态的管理:
方法 | 描述 |
---|---|
boolean tryAcquire(int arg) | 试获取独占锁 |
boolean tryRelease(int arg) | 试释放独占锁 |
int tryAcquireShared(int arg) | 试获取共享锁 |
boolean tryReleaseShared(int arg) | 试释放共享锁 |
boolean isHeldExclusively() | 当前线程是否获得了独占锁 |
以上的几个试获取/释放锁的方法的具体实现应当是无阻塞的。
AQS本身将同步状态的管理用模板方法模式都封装好了,以下列举了AQS中的一些模板方法:
方法 | 描述 |
---|---|
void acquire(int arg) | 获取独占锁。会调用tryAcquire 方法,如果未获取成功,则会进入同步队列等待 |
void acquireInterruptibly(int arg) | 响应中断版本的acquire |
boolean tryAcquireNanos(int arg,long nanos) | 响应中断+带超时版本的acquire |
void acquireShared(int arg) | 获取共享锁。会调用tryAcquireShared 方法 |
void acquireSharedInterruptibly(int arg) | 响应中断版本的acquireShared |
boolean tryAcquireSharedNanos(int arg,long nanos) | 响应中断+带超时版本的acquireShared |
boolean release(int arg) | 释放独占锁 |
boolean releaseShared(int arg) | 释放共享锁 |
Collection getQueuedThreads() | 获取同步队列上的线程集合 |
上面看上去很多方法,其实从语义上来区分就是获取和释放,从模式上区分就是独占式和共享式,从中断相应上来看就是支持和不支持。