1. FutureTask的get方法靠什么机制来阻塞
看其get方法源码:
/** * @throws CancellationException {@inheritDoc} */ public V get() throws InterruptedException, ExecutionException { return sync.innerGet(); }
不难发现,FutureTask是依靠其内部类java.util.concurrent.FutureTask.Sync<V>类来实现阻塞。
Sync又是实现了AbstractQueuedSynchronizer类。
private final class Sync extends AbstractQueuedSynchronizer
看都有谁实现了这个类:
里面有很多我们平时用到的,但是不怎么清楚其原理的类,原来都是靠实现AbstractQueuedSynchronizer来达到相应的同步机制。
AbstractQueuedSynchronizer又是靠什么来实现阻塞以及维持协调好各竞争线程间的资源分配的?看下一段。
2. 分析AbstractQueuedSynchronizer
从上面分析来看AbstractQueuedSynchronizer应该是JDK的concurrent包里比较重要的一个机制。用google先了解下他的面貌:
中文的blog的搜索结果说明有很多人对这个做了总结与学习,最下面框起来的一个pdf结果应该是对这个的一个论文。
论文中阐述了几点比较关键的地方
1. 方法命名的问题
“
同步器一般包含两种方法,一种是acquire,另一种是release。acquire操作阻塞调用的线程,直到或除非同步状态允许其继续执行。而release操作则是通过某种方式改变同步状态,使得一或多个被acquire阻塞的线程继续执行。
j.u.c包中并没有对同步器的API做一个统一的定义。因此,有一些类定义了通用的接口(如Lock),而另外一些则定义了其专有的版本。因此在不同的 类中,acquire和release操作的名字和形式会各有不同。例 如:Lock.lock,Semaphore.acquire,CountDownLatch.await和FutureTask.get,在这个框架 里,这些方法都是acquire操作。但是,J.U.C为支持一系列常见的使用选项,在类间都有个一致约定。在有意义的情况下,每一个同步器都支持下面的 操作:
阻塞和非阻塞(例如tryLock)同步。
可选的超时设置,让调用者可以放弃等待
通过中断实现的任务取消,通常是分为两个版本,一个acquire可取消,而另一个不可以。
”
2. 实现同步器的三个“组件”
“
为了实现上述操作,需要下面三个基本组件的相互协作:
a) 同步状态的原子性管理;
b) 线程的阻塞与解除阻塞;
c) 队列的管理;
”
怎么实现这三个部件,论文中有详细介绍。
论文中提到“整个框架的关键就是如何管理被阻塞的线程的队列”也就是对应上面三个“组件”的c部分。
AQS中使用了CLH队列。
为什么采用CLH而不是MCS,因为CLH更容易实现取消与超时机制。
3. CLH队列
谈CLH队列时,前面要先谈很多基础知识,不然直接阅读上面的简明介绍,会有点坡度。