Welcome 微信登录

首页 / 软件开发 / JAVA / 深度解析Java 8:JDK1.8 AbstractQueuedSynchronizer的实现分析

深度解析Java 8:JDK1.8 AbstractQueuedSynchronizer的实现分析2014-11-22 infoq 刘锟洋

前言

Java中的FutureTask作为可异步执行任务并可获取执行结果而被大家所熟知。通常可以使用future.get()来获取线程的执行结果,在线程执行结束之前,get方法会一直阻塞状态,直到call()返回,其优点是使用线程异步执行任务的情况下还可以获取到线程的执行结果,但是FutureTask的以上功能却是依靠通过一个叫AbstractQueuedSynchronizer的类来实现,至少在JDK 1.5、JDK1.6版本是这样的(从1.7开始FutureTask已经被其作者Doug Lea修改为不再依赖AbstractQueuedSynchronizer实现了,这是JDK1.7的变化之一)。但是AbstractQueuedSynchronizer在JDK1.8中还有如下图所示的众多子类:

这些JDK中的工具类或多或少都被大家用过不止一次,比如ReentrantLock,我们知道ReentrantLock的功能是实现代码段的并发访问控制,也就是通常意义上所说的锁,在没有看到AbstractQueuedSynchronizer前,可能会以为它的实现是通过类似于synchronized,通过对对象加锁来实现的。但事实上它仅仅是一个工具类!没有使用更“高级”的机器指令,不是关键字,也不依靠JDK编译时的特殊处理,仅仅作为一个普普通通的类就完成了代码块的并发访问控制,这就更让人疑问它怎么实现的代码块的并发访问控制的了。那就让我们一起来仔细看下Doug Lea怎么去实现的这个锁。为了方便,本文中使用AQS代替AbstractQueuedSynchronizer。

细说AQS

在深入分析AQS之前,我想先从AQS的功能上说明下AQS,站在使用者的角度,AQS的功能可以分为两类:独占功能和共享功能,它的所有子类中,要么实现并使用了它独占功能的API,要么使用了共享锁的功能,而不会同时使用两套API,即便是它最有名的子类ReentrantReadWriteLock,也是通过两个内部类:读锁和写锁,分别实现的两套API来实现的,为什么这么做,后面我们再分析,到目前为止,我们只需要明白AQS在功能上有独占控制和共享控制两种功能即可。

独占锁

在真正对解读AQS之前,我想先从使用了它独占控制功能的子类ReentrantLock说起,分析ReentrantLock的同时看一看AQS的实现,再推理出AQS独特的设计思路和实现方式。最后,再看其共享控制功能的实现。

对于ReentrantLock,使用过的同学应该都知道,通常是这么用它的:

reentrantLock.lock()//do somethingreentrantLock.unlock()
ReentrantLock会保证 do something在同一时间只有一个线程在执行这段代码,或者说,同一时刻只有一个线程的lock方法会返回。其余线程会被挂起,直到获取锁。从这里可以看出,其实ReentrantLock实现的就是一个独占锁的功能:有且只有一个线程获取到锁,其余线程全部挂起,直到该拥有锁的线程释放锁,被挂起的线程被唤醒重新开始竞争锁。没错,ReentrantLock使用的就是AQS的独占API实现的。

那现在我们就从ReentrantLock的实现开始一起看看重入锁是怎么实现的。

首先看lock方法:

如FutureTask(JDK1.6)一样,ReentrantLock内部有代理类完成具体操作,ReentrantLock只是封装了统一的一套API而已。值得注意的是,使用过ReentrantLock的同学应该知道,ReentrantLock又分为公平锁和非公平锁,所以,ReentrantLock内部只有两个sync的实现: