欢迎移步google drive 阅读原文:
https://drive.google.com/file/d/0B758ryeeYYQYa1k2cHhzT0ZOSlU/edit?usp=sharing
AQS全称AbstractQueuedSynchronizer,它是concurrent包中最重要的基础设施类之一,负责作为模板类向业务层提供对临界区的管理。本文以FutureTask的实现机制作为引子,介绍了AQS的业务背景和设计思路,最后梳理了AQS的代码实现。
目录
摘要............................................................................................................................... 1
动机............................................................................................................................... 2
interrupt的真相.............................................................................................................................................................. 2
FUTURE TASK问题......................................................................................................................................................... 2
内部类Sync........................................................................................................................................................................... 4
层次结构....................................................................................................................... 4
业务层...................................................................................................................................................................................... 4
复用层...................................................................................................................................................................................... 7
代码分析....................................................................................................................... 8
如何复用AQS...................................................................................................................................................................... 8
【一个例子】.............................................................................................................................................................. 11
AQS内部细节................................................................................................................................................................... 15
概述.................................................................................................................................................................................... 15
线程控制......................................................................................................................................................................... 15
队列.................................................................................................................................................................................... 15
节点.................................................................................................................................................................................... 15
排他获取......................................................................................................................................................................... 17
排他释放......................................................................................................................................................................... 21
共享获取&释放.......................................................................................................................................................... 24
回到Future Task.......................................................................................................................................................... 29
interrupt的真相
一提起interrupt,大家往往想到的是:
1、其中文直译为“中断”
2、多线程编程中无处不在的InterruptedException
或许是受到了中文翻译的误导,我们不少人会以为调用Thread.interrupt()能中断任意线程。
其实,它只是表示有人要求中断,具体的实现是更新了一个boolean型的标志位(interrupt status)。至于是否响应中断以及何时中断,都是线程自己的事了。
这就好比老师在台上讲课,下面有学生想要提问,必须先举手(请求中断)。如果老师在写板书没看到(尚未轮询到中断标志位),则什么也不会发生;如果面对学生,那么老师一般会等到一句话说完,再允许该生发言(延迟响应中断)或者宣布“讲完这一节再统一提问”(忽略并重置中断)。
所以,调用Thread.interrupt()只是“举手”而已。
有关interrupt和InterruptedException的细节,请参考此文:http://blog.csdn.net/axman/article/details/431796
正如文中所说,“只有当线程执行到sleep,wait,join等方法时,或者自己检查中断标志而抛出异常的情况下,线程才会抛出InterruptedException。”
这样做的目的是允许线程安全可控地结束,避免盲目结束出现中间状态、资源没被释放等情况。
接下来转到FutureTask。这是concurrent包里提供一个很方便的工具类,用于接受指定线程作为宿主,异步运行一个任务。其get()方法能阻塞当前用于查询结果的线程(以下简称查询线程)直到异步线程运行结束。
之前一度以为,若调用FutureTask.cancel()方法,能随时中止异步任务。
事实并非如此。就像中断机制一样,若异步任务本身没有通过Thread.isInterrupted()主动检查中断标志,那么它不仅不会结束,也不会抛出中断异常。倒是多个查询线程都能即时抛出CancellationException。
观察FutureTask.cancel()方法,其代码调用了内部类Sync.innnerCancel(),后者看起来只是简单地触发了Thread.interrupt(),这符合我们的预期。但又是怎样的机制分别唤醒了处于阻塞状态的查询线程呢?代码中releaseShared(0)引起了我的注意。
boolean innerCancel(boolean mayInterruptIfRunning) {
for(;;) {
int s= getState();
if(ranOrCancelled(s))
return false;
if(compareAndSetState(s,CANCELLED))
break;
}
if(mayInterruptIfRunning) {
Thread r = runner;
if(r != null)
r.interrupt();
}
releaseShared(0);
done();
return true;
}
为了搞清楚这个问题的来龙去脉,我展开了对FutureTask内部类Sync的调查,于是自然延伸到了其基类AbstractQueuedSynchronizer(以下简称AQS)。AQS是如此庞杂,绝非三言两语能说得清楚;更糟糕的是,网上大量有关该类的文章和学习笔记,要么蜻蜓点水一笔带过,要么一上来就跳入了代码细节的深渊,为此我绕了一个大圈才理顺。当时多么希望有一篇文章能深入浅出,用先宏观再微观的梳理方式,把这个类讲讲清楚。现在我也来写一篇AQS的学习笔记,以飨小伙伴们。
先考虑如下现实世界中的情况:
(图片引用自http://www.jq1997.cn/mkldfffiles/2012531105117204.jpg)
l 例如,男厕是临界区,有些高端大气的小便槽(上图)可供一起使用(共享访问),容纳人数有限(资源有限);保洁员“可以”在入口处放一块牌子“清洁中”(排他访问),也可以同时清洁。
l 又如,游泳池是临界区,泳客是共享访问,一场结束的时候倒消毒液的人“必须”先清场,再操作(排他访问)
l 再如,停车场是临界区,车辆均共享访问,车位(资源)有限,且小车占一个车位,大巴占两个车位。
可见,上述业务经常变化的部分是对临界区的资源管理,主要包含:
l 临界区内不同的“资源总数”;
l 每次访问占用的“资源数量”;
l 支持两种不同的“访问方式”(排他/共享)。
至于临界区被占满后,如何排队、能否插队(此处特指跳过排队直接进入临界区,注1)等管理细节都是雷同的。
接下来回到java的世界:
让我们先来看看concurrent包里常见的并发控制相关的业务类。
类名 |
功能 |
资源总数 |
如何访问 |
备注 |
Mutex |
互斥锁 |
1 |
仅排他 |
出现在AQS类的注释中 |
Semaphore |
信号量 |
可指定(构造参数permits) |
仅共享 |
|
ReentrantReadWriteLock |
读写(可重入)锁 |
无上限(实际为16位unsigned short) |
读锁共享访问,写锁排他访问 |
关于“可重入”见注2; |
注意到,表内与前述资源管理过程中