AbstractExecutorService对ExecutorService的执行任务类型的方法提供了一个默认实现。这些方法包括submit,invokeAny和InvokeAll。

注意的是来自Executor接口的execute方法是未被实现,execute方法是整个体系的核心,所有的任务都是在这个方法里被真正执行的,因此该方法的不同实现会带来不同的执行策略。这个在后面分析ThreadPoolExecutor和ScheduledThreadPoolExecutor就能看出来。

首先来看submit方法,它的基本逻辑是这样的:

1. 生成一个任务类型和Future接口的包装接口RunnableFuture的对象

2. 执行任务

3. 返回future。

  1. public Future<?> submit(Runnable task) {
  2. if (task == null) throw new NullPointerException();
  3. RunnableFuture<Void> ftask = newTaskFor(task, null);
  4. execute(ftask);
  5. return ftask;
  6. }
  7.  
  8. public <T> Future<T> submit(Callable<T> task) {
  9. if (task == null) throw new NullPointerException();
  10. RunnableFuture<T> ftask = newTaskFor(task);
  11. execute(ftask);
  12. return ftask;
  13. }

因为submit支持Callable和Runnable两种类型的任务,因此newTaskFor方法有两个重载方法:

  1. protected <T> RunnableFuture<T> newTaskFor(Callable<T> callable) {
  2. return new FutureTask<T>(callable);
  3. }
  4.  
  5. protected <T> RunnableFuture<T> newTaskFor(Runnable runnable, T value) {
  6. return new FutureTask<T>(runnable, value);
  7. }

上一篇文章里曾经说过Callable和Runnable的区别在于前者带返回值,也就是说Callable=Runnable+返回值。因此java中提供了一种adapter,把Runnable+返回值转换成Callable类型。这点可以在newTaskFor中的FutureTask类型的构造函数的代码中看到:

  1. public FutureTask(Callable<V> callable) {
  2. if (callable == null)
  3. throw new NullPointerException();
  4. sync = new Sync(callable);
  5. }
  6.  
  7. public FutureTask(Runnable runnable, V result) {
  8. sync = new Sync(Executors.callable(runnable, result));
  9. }

以下是Executors.callable方法的代码:

  1. public static <T> Callable<T> callable(Runnable task, T result) {
  2. if (task == null)
  3. throw new NullPointerException();
  4. return new RunnableAdapter<T>(task, result);
  5. }

那么RunnableAdapter的代码就很好理解了,它是一个Callable的实现,call方法的实现就是执行Runnable的run方法,然后返回那个value。

  1. static final class RunnableAdapter<T> implements Callable<T> {
  2. final Runnable task;
  3. final T result;
  4. RunnableAdapter(Runnable task, T result) {
  5. this.task = task;
  6. this.result = result;
  7. }
  8. public T call() {
  9. task.run();
  10. return result;
  11. }
  12. }

接下来先说说较为简单的invokeAll:

1. 为每个task调用newTaskFor方法生成得到一个既是Task也是Future的包装类对象的List

2. 循环调用execute执行每个任务

3. 再次循环调用每个Future的get方法等待每个task执行完成

4. 最后返回Future的list。

  1. public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks,
  2. long timeout, TimeUnit unit)
  3. throws InterruptedException {
  4. if (tasks == null || unit == null)
  5. throw new NullPointerException();
  6. long nanos = unit.toNanos(timeout);
  7. List<Future<T>> futures = new ArrayList<Future<T>>(tasks.size());
  8. boolean done = false;
  9. try {
  10. // 为每个task生成包装对象
  11. for (Callable<T> t : tasks)
  12. futures.add(newTaskFor(t));
  13.  
  14. long lastTime = System.nanoTime();
  15.  
  16. // 循环调用execute执行每个方法
  17. // 这里因为设置了超时时间,所以每次执行完成后
  18. // 检查是否超时,超时了就直接返回future集合
  19. Iterator<Future<T>> it = futures.iterator();
  20. while (it.hasNext()) {
  21. execute((Runnable)(it.next()));
  22. long now = System.nanoTime();
  23. nanos -= now - lastTime;
  24. lastTime = now;
  25. if (nanos <= 0)
  26. return futures;
  27. }
  28.  
  29. // 等待每个任务执行完成
  30. for (Future<T> f : futures) {
  31. if (!f.isDone()) {
  32. if (nanos <= 0)
  33. return futures;
  34. try {
  35. f.get(nanos, TimeUnit.NANOSECONDS);
  36. } catch (CancellationException ignore) {
  37. } catch (ExecutionException ignore) {
  38. } catch (TimeoutException toe) {
  39. return futures;
  40. }
  41. long now = System.nanoTime();
  42. nanos -= now - lastTime;
  43. lastTime = now;
  44. }
  45. }
  46. done = true;
  47. return futures;
  48. } finally {
  49. if (!done)
  50. for (Future<T> f : futures)
  51. f.cancel(true);
  52. }
  53. }

最后说说invokeAny,它的难点在于只要一个任务执行成功就要返回,并且会取消其他任务,也就是说重点在于找到第一个执行成功的任务。

这里我想到了BlockingQueue,当所有的任务被提交后,任务执行返回的Future会被依次添加到一个BlockingQueue中,然后找到第一个执行成功任务的方法就是从BlockingQueue取出第一个元素,这个就是doInvokeAny方法用到的ExecutorCompletionService的基本原理。

因为两个invokeAny方法都是调用doInvokeAny方法,下面是doInvokeAny的代码分析:

  1. private <T> T doInvokeAny(Collection<? extends Callable<T>> tasks,
  2. boolean timed, long nanos)
  3. throws InterruptedException, ExecutionException, TimeoutException {
  4. if (tasks == null)
  5. throw new NullPointerException();
  6. int ntasks = tasks.size();
  7. if (ntasks == 0)
  8. throw new IllegalArgumentException();
  9. List<Future<T>> futures= new ArrayList<Future<T>>(ntasks);
  10. // ExecutorCompletionService负责执行任务,后面调用用poll返回第一个执行结果
  11. ExecutorCompletionService<T> ecs =
  12. new ExecutorCompletionService<T>(this);
  13.  
  14. // 这里出于效率的考虑,每次提交一个任务之后,就检查一下有没有执行完成的任务
  15.  
  16. try {
  17. ExecutionException ee = null;
  18. long lastTime = timed ? System.nanoTime() : 0;
  19. Iterator<? extends Callable<T>> it = tasks.iterator();
  20.  
  21. // 先提交一个任务
  22. futures.add(ecs.submit(it.next()));
  23. --ntasks;
  24. int active = 1;
  25.  
  26. for (;;) {
  27. // 尝试获取有没有执行结果(这个结果是立刻返回的)
  28. Future<T> f = ecs.poll();
  29. // 没有执行结果
  30. if (f == null) {
  31. // 如果还有任务没有被提交执行的,就再提交一个任务
  32. if (ntasks > 0) {
  33. --ntasks;
  34. futures.add(ecs.submit(it.next()));
  35. ++active;
  36. }
  37. // 没有任务在执行了,而且没有拿到一个成功的结果。
  38. else if (active == 0)
  39. break;
  40. // 如果设置了超时情况
  41. else if (timed) {
  42. // 等待执行结果直到有结果或者超时
  43. f = ecs.poll(nanos, TimeUnit.NANOSECONDS);
  44. if (f == null)
  45. throw new TimeoutException();
  46. // 这里的更新不可少,因为这个Future可能是执行失败的情况,那么还需要再次等待下一个结果,超时的设置还是需要用到。
  47. long now = System.nanoTime();
  48. nanos -= now - lastTime;
  49. lastTime = now;
  50. }
  51. // 没有设置超时,并且所有任务都被提交了,则一直等到第一个执行结果出来
  52. else
  53. f = ecs.take();
  54. }
  55. // 有返回结果了,尝试从future中获取结果,如果失败了,那么需要接着等待下一个执行结果
  56. if (f != null) {
  57. --active;
  58. try {
  59. return f.get();
  60. } catch (ExecutionException eex) {
  61. ee = eex;
  62. } catch (RuntimeException rex) {
  63. ee = new ExecutionException(rex);
  64. }
  65. }
  66. }
  67.  
  68. // ExecutorCompletionService执行时发生错误返回了全是null的future
  69. if (ee == null)
  70. ee = new ExecutionException();
  71. throw ee;
  72.  
  73. } finally {
  74. // 尝试取消所有的任务(对于已经完成的任务没有影响)
  75. for (Future<T> f : futures)
  76. f.cancel(true);
  77. }
  78. }

后面接着分析ThreadPoolExecutor和ScheduledThreadPoolExecutor。

《java.util.concurrent 包源码阅读》10 线程池系列之AbstractExecutorService的更多相关文章

  1. 《java.util.concurrent 包源码阅读》 结束语

    <java.util.concurrent 包源码阅读>系列文章已经全部写完了.开始的几篇文章是根据自己的读书笔记整理出来的(当时只阅读了部分的源代码),后面的大部分都是一边读源代码,一边 ...

  2. 《java.util.concurrent 包源码阅读》13 线程池系列之ThreadPoolExecutor 第三部分

    这一部分来说说线程池如何进行状态控制,即线程池的开启和关闭. 先来说说线程池的开启,这部分来看ThreadPoolExecutor构造方法: public ThreadPoolExecutor(int ...

  3. 《java.util.concurrent 包源码阅读》04 ConcurrentMap

    Java集合框架中的Map类型的数据结构是非线程安全,在多线程环境中使用时需要手动进行线程同步.因此在java.util.concurrent包中提供了一个线程安全版本的Map类型数据结构:Concu ...

  4. 《java.util.concurrent 包源码阅读》02 关于java.util.concurrent.atomic包

    Aomic数据类型有四种类型:AomicBoolean, AomicInteger, AomicLong, 和AomicReferrence(针对Object的)以及它们的数组类型, 还有一个特殊的A ...

  5. 《java.util.concurrent 包源码阅读》17 信号量 Semaphore

    学过操作系统的朋友都知道信号量,在java.util.concurrent包中也有一个关于信号量的实现:Semaphore. 从代码实现的角度来说,信号量与锁很类似,可以看成是一个有限的共享锁,即只能 ...

  6. 《java.util.concurrent 包源码阅读》06 ArrayBlockingQueue

    对于BlockingQueue的具体实现,主要关注的有两点:线程安全的实现和阻塞操作的实现.所以分析ArrayBlockingQueue也是基于这两点. 对于线程安全来说,所有的添加元素的方法和拿走元 ...

  7. 《java.util.concurrent 包源码阅读》09 线程池系列之介绍篇

    concurrent包中Executor接口的主要类的关系图如下: Executor接口非常单一,就是执行一个Runnable的命令. public interface Executor { void ...

  8. 《java.util.concurrent 包源码阅读》22 Fork/Join框架的初体验

    JDK7引入了Fork/Join框架,所谓Fork/Join框架,个人解释:Fork分解任务成独立的子任务,用多线程去执行这些子任务,Join合并子任务的结果.这样就能使用多线程的方式来执行一个任务. ...

  9. 《java.util.concurrent 包源码阅读》24 Fork/Join框架之Work-Stealing

    仔细看了Doug Lea的那篇文章:A Java Fork/Join Framework 中关于Work-Stealing的部分,下面列出该算法的要点(基本是原文的翻译): 1. 每个Worker线程 ...

随机推荐

  1. python之集合

    集合(set),它是一个无序的,不重复的数据组合,它是作用如下: 1.去重,也就是去除重复的内容.有一点值得注意的是:将一个列表(list)变成集合的时候,会自动去重. 2.关系测试.测试数据之间的交 ...

  2. 前端工程化grunt

    1.grunt是什么? grunt是基于nodejs的前端构建工具.grunt用于解决前端开发的工程问题. 2.安装nodejs Grunt和所有grunt插件都是基于nodejs来运行的. 安装了n ...

  3. eval函数的用法

    可以把list,tuple,dict和string相互转化. ################################################# 字符串转换成列表 >>&g ...

  4. JS中apply和call的区别和用法

    Javascript中有一个call和apply方法,其作用基本相同,但是它们也有略微不同的地方. JS手册中对call方法的解释是: call方法:调用一个对象的一个方法,以另一个对象替换当前对象. ...

  5. Java 7 JVM和垃圾收集

    ---恢复内容开始--- 写JAVA程序,一定要了解JVM(JAVA Virtual machine)一些基础知识和垃圾收集.如果对JVM已经很了解了,可以不用继续往下阅读了.本文只针对Java 7, ...

  6. Akka(32): Http:High-Level-Api,Route exception handling

    Akka-http routing DSL在Route运算中抛出的异常是由内向外浮出的:当内层Route未能捕获异常时,外一层Route会接着尝试捕捉,依次向外扩展.Akka-http提供了Excep ...

  7. LeetCode 55. Jump Game (跳跃游戏)

    Given an array of non-negative integers, you are initially positioned at the first index of the arra ...

  8. [译]ASP.NET Core 2.0 本地文件操作

    问题 如何在ASP.NET Core 2.0中受限地访问本地目录和文件信息? 答案 新建一个空项目,修改Startup类,添加访问本地文件所需的服务: public void ConfigureSer ...

  9. python参考手册一书笔记之第一篇上

    在python2和python3的版本差异很大输出hello world的方法在2里支持在3里就不支持了. print 'hello world' #在2中支持 print ('hello world ...

  10. SpringMVC的流程分析(一)—— 整体流程概括

    SpringMVC的整体概括 之前也写过springmvc的流程分析,只是当时理解的还不透彻所以那篇文章就放弃了,现在比之前好了些,想着写下来分享下,也能增强记忆,也希望可以帮助到人,如果文章中有什么 ...