Java基础系列--Executor框架(一)
原创作品,可以转载,但是请标注出处地址:http://www.cnblogs.com/V1haoge/p/8393618.html
一、Executor框架介绍
Executor框架是JDK1.5之后出现的,位于juc包中,是并发程序设计的工具之一。各个版本以来一直在进行修正。
Executor是执行者之意,表示任务的执行者,这里的任务指的是新的线程任务(实现Runnable接口的执行任务)。
整个Executor执行者框架包括多个接口和类,甚至还涉及到阻塞队列的使用,协同实现任务的执行。
下面是简单的Executor框架的类结构:
从上面的类结构中我们可以看到Executor接口是整个框架的祖接口,它大致规划了框架的结构,并定义了执行方法execute(),这个方法需要一个Runnable作为入参,表示执行一个线程任务。从这里也可以看出来这个框架的主要思想:将要执行的任务和具体的执行进行解耦,任务的内容单独定义为一个线程,任务的执行交给该框架进行,只需要将任务提交给框架即可(这个后面会提到)。Runnable入参就表示定义为单独线程的任务内容,execute方法则是执行任务,整个框架定义的就是这样一个任务执行器,Executor框架总的来说就是一个多线程任务执行框架。
二、Executor接口
Executor接口是整个框架的总接口,正如上面所述,它描述了框架的主要实现思想:任务内容与执行的解耦。其源码很短,我们可以看看:
- package java.util.concurrent;
- public interface Executor {
- /**
- * Executes the given command at some time in the future. The command
- * may execute in a new thread, in a pooled thread, or in the calling
- * thread, at the discretion of the {@code Executor} implementation.
- *
- * @param command the runnable task
- * @throws RejectedExecutionException if this task cannot be
- * accepted for execution
- * @throws NullPointerException if command is null
- */
- void execute(Runnable command);
- }
这是一个单独的接口,其内部只有一个execute()方法。我们看看其注释:在将来某一时刻执行给定的指令,可能会在一个新的线程、或一个线程池中的线程、或在正调用的线程中执行,这取决于Executor接口的具体实现。
注意:这个方法中的入参任务指令是必不可少的,不可传null,否则会报NullPointerException(空指针异常)。
三、ExecutorService接口
ExecutorService接口继承了Executor接口,Executor接口仅仅描述了思想,定义了一个执行器,ExecutorService接口在其基础上进一步丰富了框架的接口,为框架定义了更多内容,包括:任务的提交,执行器的终止关闭等。
3.1 终止方法
- void shutdown();
- List<Runnable> shutdownNow();
如上源码,ExecutorService中定义了两个终止方法,这两个方法并不完全相同,第一个方法shutDown()的作用是终止新任务的接收,已接收的任务却需要继续执行。这是保证已提交任务全部执行的终止方法。第二个shutDownNow()方法属于强效终止方法,它会试图停止正在执行的线程任务,并且不再执行处于等待状态的其他任务,并且会将这些任务以列表的方式返回。
注意:第二个方法的试图停止,并不一定会停止,因为其实现会使用Thread.interrupt()方法来进行线程任务中断执行,但是如果任务线程不会响应该中断,则不会被终止。
3.2 任务提交方法
- <T> Future<T> submit(Callable<T> task);
- <T> Future<T> submit(Runnable task, T result);
- Future<?> submit(Runnable task);
这三个任务提交方法采用方法重载的方式定义,其实均是对execute方法的再封装,对其进行扩展而来。因为execute方法只能接受Runnable入参,切无返回值。submit提交任务却拥有返回值,而且可以接收两种格式的任务,Callable和Runnable两种。不同的方法参数和返回值也略有不同。
第一种方法接收一个Callable入参,任务执行成功会返回一个表示该任务的Future,通过其get方法可获取到在Callable任务中指定的返回内容。
第二种方法接收一个Runnable入参和一个指定的返回值,任务执行成功会返回一个表示该任务的Future,通过其get方法可以获取到之前的入参result的值,即入参result即为预设的返回值。
第三种方法接收一个Runnable入参,任务执行成功会返回一个表示该任务的Future,通过get方法可得到null。
我们通过下面的实例来进行验证:
- public static void main(String[] args) throws Exception {
- ExecutorService executor = Executors.newCachedThreadPool();
- //第一种方法:入参为Callable
- Future<String> result1 = executor.submit(new Callable<String>() {
- @Override
- public String call() throws Exception {
- return "task2";//此处的task2即为返回的内容,即future.get()的值
- }
- });
- //第二种方法:入参为Runnable和T
- Future<String> result2 = executor.submit(new Runnable() {
- @Override
- public void run() {
- System.out.println("mmp");
- }
- },"task1");//此处的task1即为返回的内容,即future.get()的值
- //第三种方法:入参为Runnable
- Future<?> result3 = executor.submit(new Runnable() {
- @Override
- public void run() {
- System.out.println("nnd");
- }
- });
- System.out.println(result1.get());
- System.out.println(result2.get());
- System.out.println(result3.get());
- }
执行结果为:
- task2
- mmp
- task1
- nnd
- null
从上面的结果中也可以看出三个方法的不同之处。
3.3 invokeAny方法
- <T> T invokeAny(Collection<? extends Callable<T>> tasks) throws InterruptedException, ExecutionException;
- <T> T invokeAny(Collection<? extends Callable<T>> tasks,long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException;
第一种方法表示执行给定的任务列表中的任务,如果某个任务成功完成,没有任何异常,则将该任务的结果返回,一旦成功或者异常被返回之后,任务列表中其他任务则取消执行,那么可以看出返回的必定是第一个执行成功的任务的结果或者最后一个任务的执行异常。
第二个方法是在第一个方法的基础上加上一个超时限制,如果在超时期满之前完成了某个任务则返回该任务的结果,其余同上。
注意:这里写到一旦成功或者异常被返回,其实这里如果第一个任务执行的时候出现了异常,则同样会被返回,同样其他任务取消执行。
例子:
- public static void main(String[] args) throws Exception {
- ExecutorService executor = Executors.newCachedThreadPool();
- List<Callable<String>> callables = new ArrayList<>();
- callables.add(new Callable<String>() {
- @Override
- public String call() throws Exception {
- return "task1";
- }
- });
- callables.add(new Callable<String>() {
- @Override
- public String call() throws Exception {
- return "task2";
- }
- });
- callables.add(new Callable<String>() {
- @Override
- public String call() throws Exception {
- return "task3";
- }
- });
- callables.add(new Callable<String>() {
- @Override
- public String call() throws Exception {
- return "task4";
- }
- });
- String s = executor.invokeAny(callables);
- System.out.println(s);
- }
执行结果:
- task1
3.4 invokeAll方法
- <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks) throws InterruptedException;
- <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks,long timeout, TimeUnit unit) throws InterruptedException;
这两个方法和3.3的两个方法结构类似,第一个方法表示执行给定的任务列表中的任务,当列表中的所有任务执行完毕之后,返回所有任务的结果组成的列表,此时列表中所有的Future中的isDone均为true,表示所有任务均被执行。
第二个方法同样在第一个方法的基础上加上了时限限制,表示在所有任务完成或者时限到期之后将所有任务的结果组成列表返回,此时列表中所有的Future中的isDone均为true
注意:如果时限期满导致返回结果的话,那些未执行的任务的结果中是null,而isDone仍然为true,状态为6-INTERRUPTED(中断),而执行成功的任务的结果状态为2-COMPLETING(完成)
例子:
- public static void main(String[] args) throws Exception {
- ExecutorService executor = Executors.newCachedThreadPool();
- List<Callable<String>> callables = new ArrayList<>();
- callables.add(new Callable() {
- @Override
- public Object call() throws Exception {
- return "task1";
- }
- });
- callables.add(new Callable() {
- @Override
- public Object call() throws Exception {
- return "task2";
- }
- });
- callables.add(new Callable() {
- @Override
- public Object call() throws Exception {
- return "task3";
- }
- });
- List<Future<String>> futures = executor.invokeAll(callables,1900L,TimeUnit.MICROSECONDS);
- for(Future<String> future:futures){
- System.out.println(future.get());
- }
- }
测试第一个方法就将22行第二个和第三个参数去掉即可,这里设置1900毫秒在我的电脑上正好能有几率测试到完成一部分就超时的情况,其执行结果为:
- task1
- task2
- at java.util.concurrent.FutureTask.report(FutureTask.java:121)
- at java.util.concurrent.FutureTask.get(FutureTask.java:192)
- at xxxTest.main(xxxTest.java:135)
其返回结果为:
全部成功的结果为:
- task1
- task2
- task3
结果为:
四、AbstractExecutorService
这是一个抽象类,实现了ExecutorService接口。这是ExecutorService的默认实现,我们来看下AbstractExecutorService中实现的方法:
4.1 newTaskFor方法
可以从中看出,AbstractExecutorService实现了之前我们介绍的ExecutorService中的大部分方法,包括三个任务提交方法,两个invokeAny和两个invokeAll方法,其中doInvokeAny方法是私有方法,被invokeAny调用,只是多出了两个newTaskFor方法。
newTaskFor方法是做什么的呢?
我们来看看源码:
- protected <T> RunnableFuture<T> newTaskFor(Runnable runnable, T value) {
- return new FutureTask<T>(runnable, value);
- }
- protected <T> RunnableFuture<T> newTaskFor(Callable<T> callable) {
- return new FutureTask<T>(callable);
- }
可以看出,这两个newTaskFor是使用给定的参数组建一个FutureTask实例并返回。所以它的作用就是提供任务执行结果Future,只是这里提供的是FutureTask类型的Future,如果我们需要使用别的RunnableFuture的实现类型(FutureTask就是RunnableFuture的实现之一),我们可以自定义。
这两个方法被submit方法所调用,用于在任务执行之前,将其包装起来,然后调用execute执行即可,之前我们看过,execute的入参是Runnable类型,此处FutureTask的超接口RunnableFuture就实现了Runnable接口。所以可以直接将包装过的任务直接作为execute的入参进行执行。
4.2 submit提交方法
下面我们就来看看三个submit方法:
- public Future<?> submit(Runnable task) {
- if (task == null) throw new NullPointerException();
- RunnableFuture<Void> 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;
- }
参考之前ExecuteService中的介绍和实例,我们可以轻松理解这里代码的含义,首先判断任务是否为null,若是null,则抛出空指针,否则使用newTaskFor将任务(和返回值)封装成为FutureTask,再将其作为入参调用execute进行任务执行。最后将之前封装的FutureTask作为返回值返回。
4.3 invokeAny方法
这里实现了invokeAny,核心是doInvokeAny方法,我们可以看下源码:
- /**
- * the main mechanics of invokeAny.
- */
- 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();
- ArrayList<Future<T>> futures = new ArrayList<Future<T>>(ntasks);
- ExecutorCompletionService<T> ecs =
- new ExecutorCompletionService<T>(this);
- // 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;
- final long deadline = timed ? System.nanoTime() + nanos : 0L;
- 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();
- 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);
- if (f == null)
- throw new TimeoutException();
- nanos = deadline - System.nanoTime();
- }
- else
- f = ecs.take();
- }
- if (f != null) {
- --active;
- try {
- return f.get();
- } catch (ExecutionException eex) {
- ee = eex;
- } catch (RuntimeException rex) {
- ee = new ExecutionException(rex);
- }
- }
- }
- if (ee == null)
- ee = new ExecutionException();
- throw ee;
- } finally {
- for (int i = 0, size = futures.size(); i < size; i++)
- futures.get(i).cancel(true);
- }
- }
解析:
1.参数校验,主要是看任务列表是否存在任务
2.优先执行一个任务,这个任务为任务列表tasks中的首个任务(30行),然后进入一个无限循环(当然会有退出条件)。
3.从执行器Executor的阻塞队列中移除队头的元素,并将该元素返回,如果队列为空队列,则这里返回值为null,此时会将优先提交的任务元素返回(35行),然后执行第53行。
4.执行器执行首个任务,执行56行,等待任务执行完成,如果任务执行成功,会在此处直接退出整个方法,一旦该任务执行出错,则会产生异常,并将异常保存在ee中(58行,60行),然后继续进行循环。
5.再次执行35行代码发现返回值为null,则会判断任务列表中的未执行任务数ntasks(该值初始为任务列表总任务数,但会随着任务的提交执行而逐渐递减,它的值就是任务列表中未提交执行的任务的数量)是否为0,此时该值一定不为0,则会执行38-40行代码,再次提交一个执行任务,然后会再次下一次循环,这次循环类似第4点。
6.一旦某个任务执行成功,就会将该任务的执行结果返回,但是一旦某个任务执行失败,则继续执行下个任务,如果所有任务都执行失败,则会将最后一个任务的失败异常抛出(这个异常将保存在ee中,一直到循环结束,由67行抛出)。
7.最后取消其他正在执行的任务(70-71行)。
总结:该方法会返回任务列表中第一个执行成功的任务的执行结果或者是抛出异常,一旦抛出了异常,表示任务全部被执行,但是全部失败。一旦某个任务执行成功,则剩下的任务将不会再执行,而且会取消其他正在执行的任务。
注意:对于有超时限制的情况,会执行44-49行代码,每个任务执行时都会进行超时判断,一旦超时期满,则抛出超时异常,并在最后取消所有正在执行的任务(70-71行)。
4.4 invokeAll方法
AbstractExecutorService中的两个invokeAll是分开实现的。如前所述,该方法用于执行一个任务列表,确保所有任务全部执行,也即All之意。
这里就先看到这里,更多内容下篇再写吧。
Java基础系列--Executor框架(一)的更多相关文章
- Java 并发编程——Executor框架和线程池原理
Eexecutor作为灵活且强大的异步执行框架,其支持多种不同类型的任务执行策略,提供了一种标准的方法将任务的提交过程和执行过程解耦开发,基于生产者-消费者模式,其提交任务的线程相当于生产者,执行任务 ...
- Java 并发编程——Executor框架和线程池原理
Java 并发编程系列文章 Java 并发基础——线程安全性 Java 并发编程——Callable+Future+FutureTask java 并发编程——Thread 源码重新学习 java并发 ...
- 夯实Java基础系列11:深入理解Java中的回调机制
目录 模块间的调用 多线程中的"回调" Java回调机制实战 实例一 : 同步调用 实例二:由浅入深 实例三:Tom做题 参考文章 微信公众号 Java技术江湖 个人公众号:黄小斜 ...
- 【Java 并发】Executor框架机制与线程池配置使用
[Java 并发]Executor框架机制与线程池配置使用 一,Executor框架Executor框架便是Java 5中引入的,其内部使用了线程池机制,在java.util.cocurrent 包下 ...
- 夯实Java基础系列1:Java面向对象三大特性(基础篇)
本系列文章将整理到我在GitHub上的<Java面试指南>仓库,更多精彩内容请到我的仓库里查看 [https://github.com/h2pl/Java-Tutorial](https: ...
- 夯实Java基础系列3:一文搞懂String常见面试题,从基础到实战,更有原理分析和源码解析!
目录 目录 string基础 Java String 类 创建字符串 StringDemo.java 文件代码: String基本用法 创建String对象的常用方法 String中常用的方法,用法如 ...
- 夯实Java基础系列4:一文了解final关键字的特性、使用方法,以及实现原理
目录 final使用 final变量 final修饰基本数据类型变量和引用 final类 final关键字的知识点 final关键字的最佳实践 final的用法 关于空白final final内存分配 ...
- 夯实Java基础系列5:Java文件和Java包结构
目录 Java中的包概念 包的作用 package 的目录结构 设置 CLASSPATH 系统变量 常用jar包 java软件包的类型 dt.jar rt.jar *.java文件的奥秘 *.Java ...
- 夯实Java基础系列6:一文搞懂抽象类和接口,从基础到面试题,揭秘其本质区别!
目录 抽象类介绍 为什么要用抽象类 一个抽象类小故事 一个抽象类小游戏 接口介绍 接口与类相似点: 接口与类的区别: 接口特性 抽象类和接口的区别 接口的使用: 接口最佳实践:设计模式中的工厂模式 接 ...
随机推荐
- 从头开始基于Maven搭建SpringMVC+Mybatis项目(4)
接上文内容,上一节中的示例中完成了支持分页的商品列表查询功能,不过我们的目标是打造一个商品管理后台,本节中还需要补充添加.修改.删除商品的功能,这些功能依靠Mybatis操作数据库,并通过Spring ...
- cs231n spring 2017 lecture11 Detection and Segmentation 听课笔记
1. Semantic Segmentation 把每个像素分类到某个语义. 为了减少运算量,会先降采样再升采样.降采样一般用池化层,升采样有各种"Unpooling"." ...
- FWT模板
代码来自51nod1570 #include<cstdio> #include<cstring> #include<algorithm> #define MN 50 ...
- Codeforces 839C Journey【DFS】
C. Journey time limit per test:2 seconds memory limit per test:256 megabytes input:standard input ou ...
- BZOJ 2748: [HAOI2012]音量调节【二维dp,枚举】
2748: [HAOI2012]音量调节 Time Limit: 3 Sec Memory Limit: 128 MBSubmit: 2010 Solved: 1260[Submit][Statu ...
- HDU5752-Sqrt Bo
Sqrt Bo Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 131072/131072 K (Java/Others)Total S ...
- hdu_1031_结构体排序
题目很好理解,将列求和,取前k大的 我的代码思路:对列求和,后取出前k大的id加入结果数组,对比后面和第k大相同的评分id也加入到结果数组,最后对结果数组排序 代码: #include<cstd ...
- layui之事件监听(table)
这几天在学习layui,感觉这框架挺好用的,前后端都适用,许多原本比较复杂的东西用该框架很容易就能实现. 今天看了table里的事件监听这个知识点. 语法:table.on('event(filter ...
- 如何查看sublime安装了哪些插件
你应该安装过package control. 那么只要这样:按ctrl+shift+p,输入package,选择list packages,就看到了. 或者直接查看Installed Packages ...
- .32-浅析webpack源码之doResolve事件流(4)
流程图如下: 重回DescriptionFilePlugin 上一节最后进入relative事件流,注入地点如下: // relative plugins.push(new DescriptionFi ...