关于java线程池 Ⅱ
上一篇翻译了线程池主要部分的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线程池 Ⅱ的更多相关文章
- Java 线程池框架核心代码分析--转
原文地址:http://www.codeceo.com/article/java-thread-pool-kernal.html 前言 多线程编程中,为每个任务分配一个线程是不现实的,线程创建的开销和 ...
- Java线程池使用说明
Java线程池使用说明 转自:http://blog.csdn.net/sd0902/article/details/8395677 一简介 线程的使用在java中占有极其重要的地位,在jdk1.4极 ...
- (转载)JAVA线程池管理
平时的开发中线程是个少不了的东西,比如tomcat里的servlet就是线程,没有线程我们如何提供多用户访问呢?不过很多刚开始接触线程的开发攻城师却在这个上面吃了不少苦头.怎么做一套简便的线程开发模式 ...
- Java线程池的那些事
熟悉java多线程的朋友一定十分了解java的线程池,jdk中的核心实现类为java.util.concurrent.ThreadPoolExecutor.大家可能了解到它的原理,甚至看过它的源码:但 ...
- 四种Java线程池用法解析
本文为大家分析四种Java线程池用法,供大家参考,具体内容如下 http://www.jb51.net/article/81843.htm 1.new Thread的弊端 执行一个异步任务你还只是如下 ...
- Java线程池的几种实现 及 常见问题讲解
工作中,经常会涉及到线程.比如有些任务,经常会交与线程去异步执行.抑或服务端程序为每个请求单独建立一个线程处理任务.线程之外的,比如我们用的数据库连接.这些创建销毁或者打开关闭的操作,非常影响系统性能 ...
- Java线程池应用
Executors工具类用于创建Java线程池和定时器. newFixedThreadPool:创建一个可重用固定线程数的线程池,以共享的无界队列方式来运行这些线程.在任意点,在大多数 nThread ...
- Java线程池的原理及几类线程池的介绍
刚刚研究了一下线程池,如果有不足之处,请大家不吝赐教,大家共同学习.共同交流. 在什么情况下使用线程池? 单个任务处理的时间比较短 将需处理的任务的数量大 使用线程池的好处: 减少在创建和销毁线程上所 ...
- Java线程池与java.util.concurrent
Java(Android)线程池 介绍new Thread的弊端及Java四种线程池的使用,对Android同样适用.本文是基础篇,后面会分享下线程池一些高级功能. 1.new Thread的弊端执行 ...
- [转 ]-- Java线程池使用说明
Java线程池使用说明 原文地址:http://blog.csdn.net/sd0902/article/details/8395677 一简介 线程的使用在java中占有极其重要的地位,在jdk1. ...
随机推荐
- hdu 3778
简单的dfs 但繁琐的可以了 0.0 #include<cstdio> #include<cstring> #include<algorithm> using s ...
- mysql 查询重复值命令
积累备忘啊: ; 从t_maintenanceinfo表查询重复记录的mtiId 和ip字段,以及重复条数
- Power Station POJ 4045
题意:给你一棵树,让你求一点,使该点到其余各点的距离之和最小.如果这样的点有多个,则按升序依次输出. 树型dp #include <cstdio> #include <cstring ...
- Python之模块篇
简介 你已经学习了如何在你的程序中定义一次函数而重用代码.如果你想要在其他程序中重用很多函数,那么你该如何编写程序呢?你可能已经猜到了,答案是使用模块.模块基本上就是一个包含了所有你定义的函数和变量的 ...
- sqlserver查询指定树形结构的所有子节点
用标准sql的with实现递归查询(sql2005以上肯定支持,sql2000不清楚是否支持): with subqry(id,name,pid) as ( select id,name,pid fr ...
- 【HDOJ】4605 Magic Ball Game
思路1:树状数组+离线处理,对所有的w离散化处理,边dfs边使用树状数组更新左右w的情况.思路2:主席树,边bfs边建树.结点信息存储cnt,然后在线查询.树状数组. /* 4605 */ #incl ...
- Linux下修改PATH的方法
Linux下修改PATH的方法 1.直接在命令行里敲 PATH=$PATH:/path1:/path2:/pathN用户登出之后PATH恢复原样. 只是临时起作用. 2.修改~目录下bash_prof ...
- IE9 表格错位bug
最近做项目的时候,出现一个只在原生IE9(非模拟)下的bug. bug图片如下: 以上两个模块的html代码和样式都是一样的,然而下面的显示却出现了各种对齐的bug. 用IE9的调试器查看,代码完全一 ...
- Maven、gradle、Ant、Eclipse IDE
Maven.gradle.Ant.Eclipse IDE之间的关系 http://wenku.baidu.com/view/d33208810912a21615792910.html?from=sea ...
- The only legal comparisons are between two numbers, two strings, or two dates.
The only legal comparisons are between two numbers, two strings, or two dates. Left hand operand is ...