代码改变世界

由FutureTask的get方法靠什么机制来阻塞引发的思考

2016-06-20 13:34  chen.simon  阅读(16212)  评论(0编辑  收藏  举报

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

看都有谁实现了这个类:

dza5vly3.q4n

里面有很多我们平时用到的,但是不怎么清楚其原理的类,原来都是靠实现AbstractQueuedSynchronizer来达到相应的同步机制。

AbstractQueuedSynchronizer又是靠什么来实现阻塞以及维持协调好各竞争线程间的资源分配的?看下一段。

2. 分析AbstractQueuedSynchronizer

从上面分析来看AbstractQueuedSynchronizer应该是JDK的concurrent包里比较重要的一个机制。用google先了解下他的面貌:

image

中文的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锁简明介绍

谈CLH队列时,前面要先谈很多基础知识,不然直接阅读上面的简明介绍,会有点坡度。

 

4. AQS思维导图

2016-11-30 09-24-56_XMind - D__600.self_05.code_04.java_21.JDK1.6_src_study_21.JDK1.6_src_study_docu

2016-11-30 09-21-50_XMind - D__600.self_05.code_04.java_21.JDK1.6_src_study_21.JDK1.6_src_study_docu

 

5. FutureTask思维导图

2016-11-30 09-22-12_XMind - D__600.self_05.code_04.java_21.JDK1.6_src_study_21.JDK1.6_src_study_docu