上一篇我们基于JDK的源码对线程池ThreadPoolExecutor的实现做了分析,本篇来对Executor框架中另一种典型用法Future方式做源码解读。我们知道Future方式实现了带有返回值的程序的异步调用,关于异步调用的场景大家可以自行脑补Ajax的应用(获取返回结果的方式不同,Future是主动询问获取,Ajax是回调函数),这里不做过多说明。


在进入源码前,首先来看下Future方式相关的API:

  • 接口Callable:有返回结果并且可能抛出异常的任务;
  • 接口Future:表示异步执行的结果;
  • 类FutureTask:实现Future、Runnable等接口,是一个异步执行的任务。可以直接执行,或包装成Callable执行;
  • 接口CompletionService:将生产新的异步任务与使用已完成任务的结果分离开来的服务,用来执行Callable或Runnable,并异步获取执行结果;
  • 类ExecutorCompletionService:实现CompletionService接口,使用构造时传入的Executor来执行Callable或Runnable,

接下来通过一个常规的使用实例来展示这些API之间的关系:

 ExecutorService executor = Executors.newFixedThreadPool(3);
CompletionService<String> completionService = new ExecutorCompletionService<String>(executor);
Future<String> future = completionService.submit(new Callable<String>() { public String call() throws Exception {
// do something...
return "success";
}
}); // 其它的程序逻辑。。。 // 异步的获取执行结果
String result = future.get();

常规调用主要是通过CompletionService.submit()方法,那我们就从这个方法开始进入JDK源码,以下是ExecutorCompletionService类的源码:

 public Future<V> submit(Callable<V> task) {
if (task == null) throw new NullPointerException();
RunnableFuture<V> f = newTaskFor(task);
executor.execute(new QueueingFuture(f));
return f;
} public Future<V> submit(Runnable task, V result) {
if (task == null) throw new NullPointerException();
RunnableFuture<V> f = newTaskFor(task, result);
executor.execute(new QueueingFuture(f));
return f;
}

sunmit()方法有两个重载,这里我们只对参数为Callable的方法解读,因为另一个也是间接的封装成了Callable最后调用的。

上述代码的第3行可以看到,Callable转换成了RunnableFuture来交给executor执行,下面来看newTaskFor(task)方法的源码:

 private RunnableFuture<V> newTaskFor(Callable<V> task) {
if (aes == null)
return new FutureTask<V>(task);
else
return aes.newTaskFor(task);
}

代码第3行和第5行的结果是一样的,继续来看这个FutureTask如何构造的:

 public FutureTask(Callable<V> callable) {
if (callable == null)
throw new NullPointerException();
sync = new Sync(callable);
}

还是没什么用,然后我们详细的来看下Sync的源码:

 /**
* Synchronization control for FutureTask. Note that this must be
* a non-static inner class in order to invoke the protected
* <tt>done</tt> method. For clarity, all inner class support
* methods are same as outer, prefixed with "inner".
*
* Uses AQS sync state to represent run status
*/
private final class Sync extends AbstractQueuedSynchronizer { /** The underlying callable */
private final Callable<V> callable;
/** The result to return from get() */
private V result;
/** The exception to throw from get() */
private Throwable exception; Sync(Callable<V> callable) {
this.callable = callable;
} V innerGet() throws InterruptedException, ExecutionException {
acquireSharedInterruptibly(0);
if (getState() == CANCELLED)
throw new CancellationException();
if (exception != null)
throw new ExecutionException(exception);
return result;
} void innerSet(V v) {
for (;;) {
int s = getState();
if (s == RAN)
return;
if (s == CANCELLED) {
// aggressively release to set runner to null,
// in case we are racing with a cancel request
// that will try to interrupt runner
releaseShared(0);
return;
}
if (compareAndSetState(s, RAN)) {
result = v;
releaseShared(0);
done();
return;
}
}
} void innerSetException(Throwable t) {
for (;;) {
int s = getState();
if (s == RAN)
return;
if (s == CANCELLED) {
// aggressively release to set runner to null,
// in case we are racing with a cancel request
// that will try to interrupt runner
releaseShared(0);
return;
}
if (compareAndSetState(s, RAN)) {
exception = t;
result = null;
releaseShared(0);
done();
return;
}
}
} void innerRun() {
if (!compareAndSetState(0, RUNNING))
return;
try {
runner = Thread.currentThread();
if (getState() == RUNNING) // recheck after setting thread
innerSet(callable.call());
else
releaseShared(0); // cancel
} catch (Throwable ex) {
innerSetException(ex);
}
} // others codes
}

上述代码列出了几个重要的方法,可以大概的看出,Future方式的玄机,基本都在这个内部类里了。下面就对这个内部类中的几个方法少做解释:

首先,类的注释主要说明了:内部类必须是非静态的,是为了调用外部类的done()方法(这个我们以后再说)。还有内部类的方法都是以“前缀inner+外部类方法名”来命名的。

其次,通过查看外部类的源码可得知:外部类的所有方法都是通过内部类中同名的inner方法来调用的(源码很简单这里没有列出)。

然后,我们来看这个类中的其中3个成员变量及其注释,就可以大概猜到:callable是传入的执行过程,result用来存储callable的返回值,exception存储callable抛出的异常(如果有)。

最后,我们来分别看这个类中的几个关键方法:

上述代码第74行的innerRun()方法:注意外部类是FutureTask,实现了Runnable接口,事实上就是最开始所说的submit()方法中,最终要执行的Runnable任务,此时执行的其实是内部类的innerRun(),通过代码的第80行可以看出,是调用了callable.call()并把返回值通过innerSet(V v)赋值给了成员变量result。如果callable.call()有异常,则通过innerSetException(Throwable t)赋值给成员变量exception。

通过innserGet()方法差不多就知道了异步获取执行结果的原理了,第23行的acquireSharedInterruptibly(0)方法的意义在于:要等Runnable任务执行完或被中断才能执行后面的代码。(注:这块实现虽然被一笔带过,但其实逻辑还是有点复杂,其实现主要是用死循环检查执行Callable线程的状态,类似自旋锁的概念)

最后,在回过头来看CompletionService.submit()方法:

 public Future<V> submit(Callable<V> task) {
if (task == null) throw new NullPointerException();
RunnableFuture<V> f = newTaskFor(task);
executor.execute(new QueueingFuture(f));
return f;
}

代码的第4行并非执行的是我们上面说的FutureTask,而是将这个FutureTask由封装成了QueueingFuture才交给executor执行,当我们看了QueueingFuture的源码就会了解到

 /**
* FutureTask extension to enqueue upon completion
*/
private class QueueingFuture extends FutureTask<Void> {
QueueingFuture(RunnableFuture<V> task) {
super(task, null);
this.task = task;
}
protected void done() { completionQueue.add(task); }
private final Future<V> task;
}

这个封装的意义无非是想在callable.call()执行完后调用第9行的completionQueue.add(task),done()方法是不是很眼熟?

注释其实已经说明了:是为了让完成的任务入列到completionQueue中,以实现本文最开始罗列出来的,CompletionService接口的“将新的异步任务与完成的任务分离开来”的特性。

总结

本文通过ExecutorCompletionService类与FutureTask类及其内部类中部分关键处源码的解读,介绍了Java5中Future方式的原理。其实可以概括为一句话:

  • 在某线程中执行Callable时,将执行结果或抛出的异常存放在临时变量中,其它线程在Callable执行完或中断前,阻塞的获取执行结果。

关于Executor框架的源码就解读到这,下篇文章开始一些工具类的源码解析。

JDK源码分析之concurrent包(三) -- Future方式的实现的更多相关文章

  1. JDK源码分析之concurrent包(一) -- Executor架构

    Java5新出的concurrent包中的API,是一些并发编程中实用的的工具类.在高并发场景下的使用非常广泛.笔者在这做了一个针对concurrent包中部分常用类的源码分析系列.本系列针对的读者是 ...

  2. JDK源码分析之concurrent包(二) -- 线程池ThreadPoolExecutor

    上一篇我们简单描述了Executor框架的结构,本篇正式开始并发包中部分源码的解读. 我们知道,目前主流的商用虚拟机在线程的实现上可能会有所差别.但不管如何实现,在开启和关闭线程时一定会耗费很多CPU ...

  3. JDK源码分析之concurrent包(四) -- CyclicBarrier与CountDownLatch

    上一篇我们主要通过ExecutorCompletionService与FutureTask类的源码,对Future模型体系的原理做了了解,本篇开始解读concurrent包中的工具类的源码.首先来看两 ...

  4. JDK源码分析(三)—— LinkedList

    参考文档 JDK源码分析(4)之 LinkedList 相关

  5. JDK源码分析—— ArrayBlockingQueue 和 LinkedBlockingQueue

    JDK源码分析—— ArrayBlockingQueue 和 LinkedBlockingQueue 目的:本文通过分析JDK源码来对比ArrayBlockingQueue 和LinkedBlocki ...

  6. 【JUC】JDK1.8源码分析之ArrayBlockingQueue(三)

    一.前言 在完成Map下的并发集合后,现在来分析ArrayBlockingQueue,ArrayBlockingQueue可以用作一个阻塞型队列,支持多任务并发操作,有了之前看源码的积累,再看Arra ...

  7. 【JDK】JDK源码分析-HashMap(1)

    概述 HashMap 是 Java 开发中最常用的容器类之一,也是面试的常客.它其实就是前文「数据结构与算法笔记(二)」中「散列表」的实现,处理散列冲突用的是“链表法”,并且在 JDK 1.8 做了优 ...

  8. 【JDK】JDK源码分析-ArrayList

    概述 ArrayList 是 List 接口的一个实现类,也是 Java 中最常用的容器实现类之一,可以把它理解为「可变数组」. 我们知道,Java 中的数组初始化时需要指定长度,而且指定后不能改变. ...

  9. 【JDK】JDK源码分析-AbstractQueuedSynchronizer(2)

    概述 前文「JDK源码分析-AbstractQueuedSynchronizer(1)」初步分析了 AQS,其中提到了 Node 节点的「独占模式」和「共享模式」,其实 AQS 也主要是围绕对这两种模 ...

随机推荐

  1. ASP.NET CORE 2.1无法添加控制器、视图

    常规操作:右键Controllers,添加控制器 结果提示:运行所选代码生成器时出错 在控制器里快速添加视图也得到同样的错误提示 既然提示代码生成器了,对比了一下2.0和2.1的代码,发现2.1默认没 ...

  2. Caused by: java.lang.NoSuchMethodException: <init> [class android.content.Context, interface android.util.AttributeSet] 异常

    1. 异常描述 FATAL EXCEPTION: main Process: com.whereru.greengrass.goforit, PID: 13847 java.lang.RuntimeE ...

  3. 【原创】k8s源代码分析-----kubelet(8)pod管理

    本文QQ空间链接:http://user.qzone.qq.com/29185807/blog/1460540474 本文csdn博客链接:http://blog.csdn.net/screscent ...

  4. pandas所占内存释放

    df = pd.read_csv('....') 要调用循环处理多个文件时,内存占用情况严重,如果互相之间不需要调用,可以直接del df  释放内存

  5. Lintcode---区间最小数

    给定一个整数数组(下标由 0 到 n-1,其中 n 表示数组的规模),以及一个查询列表.每一个查询列表有两个整数 [start, end]. 对于每个查询,计算出数组中从下标 start 到 end ...

  6. JVM性能调优入门

    1. 背景 虽然大多数应用程序使用JVM的默认设置就能很好地工作,仍然有不少应用程序需要对JVM进行额外的配置才能达到其期望的性能要求. 现在JVM为了满足各种应用的需要,为程序运行提供了大量的JVM ...

  7. Atitit.分区对索引的影响 分区索引和全局索引 attilax总结

    Atitit.分区对索引的影响 分区索引和全局索引 attilax总结 1. 分区的好处1 2. 分区键:2 3. 分区的建议:2 4. 分区索引和全局索引:2 5. 全局索引就是在全表上创建索引, ...

  8. datatables 相关文章

    http://blog.csdn.net/zhu_xiao_yuan/article/details/51252300 datatables参数配置详解  http://blog.csdn.net/j ...

  9. PLS-00157: AUTHID only allowed on schema-level programs解决办法 包体的过程使用调用者权限方法

    在包体里写了一个过程,test执行时报错,但是如果把该过程单独拿出来创建一个,就能顺利执行. 在没加上调用者权  authid current_user之前,报错如下 ORA-01031: insuf ...

  10. MySQL 错误号码 1449

    出现类似的问题是由于权限问题,授权给root所有sql权限即可: mysql> grant all privileges on *.* to root@"%" identif ...