上一篇翻译了线程池主要部分的api,经过一段时间的学习,这里记录一下这段时间对jdk自带线程池的学习成果。

为了方便说明,先放一张类图,包括了jdk线程池主要涉及到的类,为了条理清晰去掉了部分依赖和关联关系

说明

概述

把握住主要的几个类可以比较快的了解整个实现的思想,下面罗列一下,细节的部分会在之后详细描述:

主要的接口定义:Executor,Future,ExecutorService,Runnable

主要的抽象类:AbstractExecutorService

主要的实现类:ThreadPoolExecutor

模型骨架

按顺序讲讲我理解的这些代码的coder也就是Doug Lea在编写这部分代码的时候的思路:

1.首先想到的是最抽象的定义,应该有个执行者,这个执行者的行为只有一个,就是执行可以运行的(Runnable)的任务,出于此目的,他设计了Executor接口,定义了一个行为execute(Runnable command).

2.因为Runnable接口只能保证一个任务(task)是可执行的,并不能记录这个任务执行期间实时的状态及执行结果等特性,并且也不能够提供类似取消执行任务这样的操作,所以需要另设一个接口,通过这个接口来保证前面所描述的这些特性能够实现,在这样的前提下,诞生了Future接口,具体接口行为定义如下:

1.cancel(boolean mayInterruptIfRunning):取消执行操作,这个取消操作对队列中尚在等待的任务有效,已经执行的任务在调用此方法的时候将会返回false。但是,若将mayInterruptIfRunning方法的值设置为true时,将会中断正在执行的任务,正在执行的任务应当抛出InterruptException.

2.isCancelled():判断当前任务是否已经取消

3.isDone():判断当前任务是否完成

4.get():调用此方法的线程,在必要的情况下,等待执行任务的线程执行完毕,并最终获取返回结果。

5.get(long timeout,TimeUnit unit):调用此方法的线程,在一定的时间内等待执行任务的线程执行完毕,指定时间内未能获取返回结果将最终导致抛出TimeOutException异常。

3.定义了Executor,Future这两类元素,再加上Runnable元素,模型的草图就差不多设计完成了,但是实际上Executor并不能与Future协同工作,记得吗?Executor只有一个行为,execute(Runnable command).Executor实际上只能与Runnable协同工作,那么为了使Executor同时还能与Future协同工作,ExecutorService接口诞生了,具体接口行为定义如下:

1.shutdown():关闭当前的ExecutorService,这个方法允许等待执行的任务执行完毕。

2.shutdownNow():关闭当前的ExecutorService,这个方法会阻止等待执行的任务启动,并且会尝试中断正在执行的任务。

3.isShutdown():判断当前ExecutorService是否已经关闭

4.isTerminated():判断在执行了shutdown操作之后是否所有的任务都已经完成了。(不管是普通的执行完毕还是抛出异常都算执行终止)

5.awaitTermination(long timeout,TimeUnit unit):等待在执行了shutdown操作之后剩余的等待执行的任务执行完毕,这个操作将会在指定时间内阻塞线程,直到返回结果或者超时。

6.submit(Callable<T> task):Future<T>  :提交一个Callable任务,并返回一个Future对象,如果你想在这个方法上阻塞只到这个任务执行完毕,你可以这么写:exec.submit(task).get();

7.submit(Runnable task,T result):Future<T>   :这个方法与6类似,只是因为提交的是Runnable任务,这个任务自身执行完毕是没有返回值的,所以需要自己设置返回结果,即result;

8.submit(Runnable taks):Future<T>   :还是同上,只是放回的Future<T>执行get()方法时将会返回null.

9.invokeAll(Collection<? extends Callable<T> tasks):List<Future<T>>:批量的执行Callable任务,并返回一个List<Future<T>>

10.invokeAll(Collection<? extends Callable<T> tasks,long  timeout, TimeUnit unit):List<Future<T>>  :猜也能猜个八九不离十了,在指定时间内批量的执行Callable任务,并返回一个List<Future<T>>,接口定义中这样描述,不管是所有任务完成或者是超时,都将返回List<Future<T>>。这意味着,这个Future的list中会有一部分Future指示的任务可能是未完成的。

11.invokeAny(Collection<? extends Callable<T> tasks>):T  :这个行为比较有意思,批量执行Callable任务(不保证全部执行),返回其中执行成功的一个任务的执行结果。一旦有任意一个任务成功返回,或者抛出异常,则其他还未完成的任务将被取消。

12.invokeAny(Collection<? extends Callable<T> tasks>, long timeout, TimeUnit unit):和上一个行为是一样的,只是限制了再一定时间内完成。

实际上到了这一步,整个模型的骨架已经搭建起来了。但是,到目前为止可以看出的是,实际上这个模型搭建的是单个执行者(Executor),多个任务(Runnable或者Callable)的工作场景。多个执行者,多个任务的工作场景,实际上是由ThreadPoolExecutor继承了AbstractExecutorService而实现的。下面我们主要看看这两个类。

详细设计

AbstractExecutorService

先着重介绍AbstractExecutorService,这个抽象类实现了ExecutorService的submit,invokeAll,invokeAny方法,即所有处理任务的方法(但是要注意,它并没有实现Executor的execute方法,这个方法将在ThreadPoolExecutor中实现,这里的所有处理任务的方法都只是对execute方法的包装)。

这个类中有两个有意思的设计,我们主要看看这两个设计:

1.这个类上有一个有意思的变化,还记得之前说的ExecutorService是可以同时Runnable与Future协同工作的吗?从类图上可以看出来,实际上AbstractExecutorService依赖于FutureTask这个类,而这个类又实现了RunnableFuture接口,而RunnableFuture接口继承了Future接口和Runnable接口!看出来了没?FutureTask实际上是一个Future与Runnable的混合产物!FutureTask即具有Future的行为,也具有Runnable的行为。这样就方便了,不管是想要Future作为返回值,还是想要Runnable作为参数,都只需要提供一个FutureTask就可以满足需要了,同时,统一使用FutureTask最主要的目的,按照我的思路,应该是屏蔽Future对象与Runnable对象之间的信息交流,减少实际编写时的代码开销(即把Future与Runnable对象的信息交互统一到了FutureTask中)

AbstractExecutorService提供了两个方法来将传入的Runnable对象和Callable对象转换成FutureTask对象,如下所示:

/**
* Returns a <tt>RunnableFuture</tt> for the given runnable and default
* value.
*
* @param runnable the runnable task being wrapped
* @param value the default value for the returned future
* @return a <tt>RunnableFuture</tt> which when run will run the
* underlying runnable and which, as a <tt>Future</tt>, will yield
* the given value as its result and provide for cancellation of
* the underlying task.
* @since 1.6
*/
protected <T> RunnableFuture<T> newTaskFor(Runnable runnable, T value) {
return new FutureTask<T>(runnable, value);
} /**
* Returns a <tt>RunnableFuture</tt> for the given callable task.
*
* @param callable the callable task being wrapped
* @return a <tt>RunnableFuture</tt> which when run will call the
* underlying callable and which, as a <tt>Future</tt>, will yield
* the callable's result as its result and provide for
* cancellation of the underlying task.
* @since 1.6
*/
protected <T> RunnableFuture<T>

newTaskFor(Callable<T>

 callable) {
return new FutureTask<T>(callable);
}

submit方法通过调用newTaskFor方法获取FutureTask对象,在调用execute(Runnable task)时,使用了FutureTask对象作为传入参数,返回时也使用了同一个FutureTask对象作为返回值,这样就对外屏蔽了Future与Runnable之间的信息交流,不必再在每个submit方法中都将返回的结果拼装成Future对象,然后返回。如下所示:

public Future<?> submit(Runnable task) {
if (task == null) throw new NullPointerException();
RunnableFuture<Object> ftask =

newTaskFor(task,

 null);
execute(ftask);
return ftask;
} public <T> Future<T> submit(Runnable task, T result) {
if (task == null) throw new NullPointerException();
RunnableFuture<T> ftask = newTaskFor(task, result);
execute(ftask);
return ftask;
} public <T> Future<T>

submit(Callable<T>

 task) {
if (task == null) throw new NullPointerException();
RunnableFuture<T> ftask = newTaskFor(task);
execute(ftask);
return ftask;
}

这里顺带一提FutureTask的实现:应该不难发现,我们讲到这里为止,都没有说明,如果出现共享资源竞争,即多个线程同时执行时,线程安全问题怎么解决?这个问题实际上就是在FutureTask中解决的。FutureTask有个私有属性 sync,FutureTask的所有行为实际上都是由sync实现的。而这个sync所属的类Sync实际上是FutureTask的内部类.这个类继承了AbstractQueuedSynchronizer 。Sync可以保证在共享资源竞争时,有且仅有一个线程获得资源(这一部分我打算下一次仔细研究过以后再放上博客)。

2.AbstractExecutorService在实现invokeAny方法时,引入了ExecutorCompletionService。

先从为何这么设计说起。回头看看invokeAny的接口行为定义:批量执行Callable任务(不保证全部执行),返回其中执行成功的一个任务的执行结果。一旦有任意一个任务成功返回,或者抛出异常,则其他还未完成的任务将被取消。也就是说,要想实现这个行为,必须得知道当前是否有任意一个任务完成,那么怎么来判断是否有任意一个任务是否完成呢?(注意:是判断有没有任意一个任务完成,而不是判断当前任务是否完成,否则可以直接调用FutureTask的isDone()方法即可判断当前任务是否完成)可以看看FutureTask是否实现了这么一个方法。进入FutureTask的类,可以看到并没有这么一个方法,但是,其中有一个方法值得注意,如下所示:

/**
* Protected method invoked when this task transitions to state
* <tt>isDone</tt> (whether normally or via cancellation). The
* default implementation does nothing. Subclasses may override
* this method to invoke completion callbacks or perform
* bookkeeping. Note that you can query status inside the
* implementation of this method to determine whether this task
* has been cancelled.
*/
protected void done() { }

这里实现了一个空的done()方法,根据注释,这个方法将会在任务状态变为完成的时候执行。那么就好办了,只需要实现一个类继承FutureTask然后实现done()方法,将已经完成的任务放入一个队列中,然后监控这个队列,一旦这个队列有值,就说明有一个任务完成了,invokeAny方法即可以返回这个完成的任务的结果了。

这个就是ExecutorCompletionService诞生的原因。ExecutorCompletionService类有一个内部类 QueueingFuture 继承自FutureTask,它实现了done()方法,将完成的任务加入了一个LinkedBlockingQueue<Future<V>>中。如下所示:

/**
* 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;
}

ExecutorCompletionService的submit方法为真正任务的执行者提供了一个QueueingFuture,并提供了一系列监控已完成任务队列:completionQueue的方法。这样在实际处理当中,就可以通过监控ExecutorCompletionService的completionQueue来判断是否有任务完成,这部分的具体代码在AbstractEexcutorService的

private <T> T doInvokeAny(Collection<? extends Callable<T>> tasks,boolean timed, long nanos)中,如下所示:

private <T> T doInvokeAny(Collection<? extends Callable<T>> tasks,
boolean timed, long nanos)
throws InterruptedException, ExecutionException, TimeoutException {
if (tasks == null)
throw new NullPointerException();
int ntasks = tasks.size();
if (ntasks == 0)
throw new IllegalArgumentException();
List<Future<T>> futures= new ArrayList<Future<T>>(ntasks);
ExecutorCompletionService<T> ecs =
new ExecutorCompletionService<T>(this); //用ExecutorCompletionService对自己做了一层包装 // For efficiency, especially in executors with limited
// parallelism, check to see if previously submitted tasks are
// done before submitting more of them. This interleaving
// plus the exception mechanics account for messiness of main
// loop. try {
// Record exceptions so that if we fail to obtain any
// result, we can throw the last exception we got.
ExecutionException ee = null;
long lastTime = (timed)? System.nanoTime() : 0;
Iterator<? extends Callable<T>> it = tasks.iterator(); // Start one task for sure; the rest incrementally
futures.add(ecs.submit(it.next()));
--ntasks;
int active = 1; for (;;) {
Future<T> f = ecs.poll(); //ExecutorCompletionService提供的监控completionQueue的方法,如果队列为空则不进行等待,直接返回null
if (f == null) {
if (ntasks > 0) {
--ntasks;
futures.add(ecs.submit(it.next()));
++active;
}
else if (active == 0)
break;
else if (timed) {
f = ecs.poll(nanos, TimeUnit.NANOSECONDS); //ExecutorCompletionService提供的监控completionQueue的方法,如果队列为空则等待指定的时间,如果超时时队列仍然为空,则返回null
if (f == null)
throw new TimeoutException();
long now = System.nanoTime();
nanos -= now - lastTime;
lastTime = now;
}
else
f = ecs.take(); //ExecutorCompletionService提供的监控completionQueue的方法,如果队列为空会一直等待直到队列中有值,然后取到值返回
}
if (f != null) {
--active;
try {
return f.get();
} catch (InterruptedException ie) {
throw ie;
} catch (ExecutionException eex) {
ee = eex;
} catch (RuntimeException rex) {
ee = new ExecutionException(rex);
}
}
} if (ee == null)
ee = new ExecutionException();
throw ee; } finally {
for (Future<T> f : futures)
f.cancel(true);
}
}

以上就是AbstractExecutorServcie中两个值得注意的地方,剩下的invokeAll方法由于比较简单就不介绍了。下面介绍一下真正实现线程池的关键类,ThreadPoolExecutor。

ThreadPoolExecutor

感觉这章要写不完了。。另起一章讲这个ThreadPoolExecutor吧。。

关于java线程池 Ⅱ的更多相关文章

  1. Java 线程池框架核心代码分析--转

    原文地址:http://www.codeceo.com/article/java-thread-pool-kernal.html 前言 多线程编程中,为每个任务分配一个线程是不现实的,线程创建的开销和 ...

  2. Java线程池使用说明

    Java线程池使用说明 转自:http://blog.csdn.net/sd0902/article/details/8395677 一简介 线程的使用在java中占有极其重要的地位,在jdk1.4极 ...

  3. (转载)JAVA线程池管理

    平时的开发中线程是个少不了的东西,比如tomcat里的servlet就是线程,没有线程我们如何提供多用户访问呢?不过很多刚开始接触线程的开发攻城师却在这个上面吃了不少苦头.怎么做一套简便的线程开发模式 ...

  4. Java线程池的那些事

    熟悉java多线程的朋友一定十分了解java的线程池,jdk中的核心实现类为java.util.concurrent.ThreadPoolExecutor.大家可能了解到它的原理,甚至看过它的源码:但 ...

  5. 四种Java线程池用法解析

    本文为大家分析四种Java线程池用法,供大家参考,具体内容如下 http://www.jb51.net/article/81843.htm 1.new Thread的弊端 执行一个异步任务你还只是如下 ...

  6. Java线程池的几种实现 及 常见问题讲解

    工作中,经常会涉及到线程.比如有些任务,经常会交与线程去异步执行.抑或服务端程序为每个请求单独建立一个线程处理任务.线程之外的,比如我们用的数据库连接.这些创建销毁或者打开关闭的操作,非常影响系统性能 ...

  7. Java线程池应用

    Executors工具类用于创建Java线程池和定时器. newFixedThreadPool:创建一个可重用固定线程数的线程池,以共享的无界队列方式来运行这些线程.在任意点,在大多数 nThread ...

  8. Java线程池的原理及几类线程池的介绍

    刚刚研究了一下线程池,如果有不足之处,请大家不吝赐教,大家共同学习.共同交流. 在什么情况下使用线程池? 单个任务处理的时间比较短 将需处理的任务的数量大 使用线程池的好处: 减少在创建和销毁线程上所 ...

  9. Java线程池与java.util.concurrent

    Java(Android)线程池 介绍new Thread的弊端及Java四种线程池的使用,对Android同样适用.本文是基础篇,后面会分享下线程池一些高级功能. 1.new Thread的弊端执行 ...

  10. [转 ]-- Java线程池使用说明

    Java线程池使用说明 原文地址:http://blog.csdn.net/sd0902/article/details/8395677 一简介 线程的使用在java中占有极其重要的地位,在jdk1. ...

随机推荐

  1. js的基本概念详解

    来自<javascript高级程序设计 第三版:作者Nicholas C. Zakas>的学习笔记(三) 如果你刚学js,想快速了解到js的基本概念,以下将会是一篇不错的引导文章: 语法 ...

  2. MVC中的@Html.DisplayFor等方法如何控制日期的显示格式(转) - Rising

    在Sql Server2005中,如果将某字段定义成日期  时间   类型DateTime,那么在视图中会默认显示成年月日时分秒的方式(如    2013/8/6 13:37:33) 如果只想显示成年 ...

  3. 关于NGUI中的自适应和对齐机制

    原地址:http://blog.sina.com.cn/s/blog_62df69790101gy7t.html 1 关于美术效果图 美术出效果图总是基于某个固定尺寸.移动设备的分辨率却有太多不同的方 ...

  4. java jdbc dbcp连接SQL Server

    使用到的jar: commons-collections-3.1.jar commons-dbcp-1.4.jar commons-pool-1.5.6.jar sqljdbc4.jar dbcp配置 ...

  5. ASC #1

    开始套题训练,第一套ASC题目,记住不放过每一题,多独立思考. Problem A ZOJ 2313 Chinese Girls' Amusement 循环节 题意:给定n,为圆环长度,求k < ...

  6. UIcollectionView的使用(首页的搭建2)

    2.2 直接购买的UIcollectionCell 2.2.1创建CFPromptBuyCell,继承自UICollectionViewCell,定义了标题和图片两个属性 2.2.2 在.m文件中定义 ...

  7. 使用程序获取整型数据和浮点型数据在内存中的表示---gyy整理

    使用程序获取整型数据和浮点型数据在内存中的表示. C++中整型(int).短整型(short int).单精度浮点数(float).双精度浮点数(double)在内存中所占字节数不同,因此取值范围也不 ...

  8. Consistent Hashing算法-搜索/负载均衡

    在做服务器负载均衡时候可供选择的负载均衡的算法有很多,包括:  轮循算法(Round Robin).哈希算法(HASH).最少连接算法(Least Connection).响应速度算法(Respons ...

  9. VC 设置 Stack Overflow

    C/C++ stack overflow, 怎样设置stack大小?解决方案 (1) vc6.0: project->setting->link->project options-& ...

  10. 三维软件转Unity的系统单位设置研究

    Unity的系统单位为米,其他3D软件的模型导入,而保持和Unity的比例一致是非常重要的,下面对各软件进行测试: ㈠. 3dsmax 转 Unity的比例为100:1:也就是说Unity单位是3ds ...