七、ExecutorService的API
七、ExecutorService的API7.1API overview首先来概览一下,这个接口大概有哪些重要API:invokeAny() 与 invokeAll() 具有阻塞特性invokeAny()取得第一个完成任务的结果值,当第一个任务执行完成后,调用interrupt() 中断其他任务,所以可以结合if(Thread.intterruptted())决定任务是否继续运...
七、ExecutorService的API
7.1 API overview
首先来概览一下,这个接口大概有哪些重要API:
invokeAny()
与invokeAll()
具有阻塞特性invokeAny()
取得第一个完成任务的结果值,当第一个任务执行完成后,调用interrupt()
中断其他任务,所以可以结合if(Thread.intterruptted())
决定任务是否继续运行。invokeAll()
等全部线程任务执行完成后,取得全部完成任务的结果值。
7.2 invokeAny
invokeAny
、invokeAll
都具有阻塞特性
1、invokeAny(Collection tasks) 与interrupted状态
- tasks执行完任意一个,才会唤醒主线程。并且会返回 第一个执行完任务的结果。
- tasks中假设有 t1 ,t2 两个任务,t1快 t2慢,则t1完成时,会给t2 状态设置为
interrupted
。可在t2 中增加if(Thread.interrputed)
的判断,中断 正在执行的t2线程。(见demo7.2.1)
// demo7.2.1
public class MyExecutorService {
public static void main(String[] args) throws ExecutionException, InterruptedException {
List<Callable<String>> list = new ArrayList<>();
list.add(new MyCallableA());
list.add(new MyCallableB1());
ExecutorService executorService = Executors.newFixedThreadPool(10);
// invokeAny --> 只取最先完成任务的结果值
// 从结果来看就是:打印出 any 以后,主线程继续运行,而 MyCallableB1 线程继续执行完
// invokeAny --> 此方法具有阻塞特性,主线程会在此阻塞
String any = executorService.invokeAny(list);
System.out.println("invokeAny method gets value " + any);
System.out.println("main thread ends here!");
}
}
class MyExecutorService2{
public static void main(String[] args) throws ExecutionException, InterruptedException {
List<Callable<String>> list = new ArrayList<>();
list.add(new MyCallableA());
list.add(new MyCallableB2());
ExecutorService executorService = Executors.newFixedThreadPool(10);
// invokeAny --> 只取最先完成任务的结果值
// 从结果来看就是:打印出 any 以后,主线程继续运行,而 MyCallableB2 线程继续执行完
//而由于 MyCallableB2 中会对 当前线程状态做出判断,假如是已标记为“ interrupted” ,就
// 抛出异常中断,需要注意的是: MyCallableB2 中断后,异常并不会传递到 主线程
// invokeAny --> 此方法具有阻塞特性,主线程会在此阻塞
String any = executorService.invokeAny(list);
System.out.println("invokeAny method gets value " + any);
System.out.println("main thread ends here!");
}
}
class MyCallableA implements Callable<String>{
@Override
public String call() throws Exception {
System.out.println("MyCallableA begins " + System.currentTimeMillis());
IntStream.range(0, 10_00).forEach(e -> {
Math.random();Math.random();Math.random();
System.out.println("MyCallableA " + (e + 1));
});
System.out.println("MyCallableA ends " + System.currentTimeMillis());
return " return A ";
}
}
class MyCallableB1 implements Callable<String> {
@Override
public String call() throws Exception {
System.out.println("MyCallableB1 begins " + System.currentTimeMillis());
IntStream.range(0, 20_00).forEach(e -> {
Math.random();Math.random();Math.random();
System.out.println("MyCallableB1 " + (e + 1));
});
System.out.println("MyCallableB1 ends " + System.currentTimeMillis());
return "return B1 ";
}
}
class MyCallableB2 implements Callable<String> {
@Override
public String call() throws Exception {
System.out.println("MyCallableB2 begins " + System.currentTimeMillis());
for (int e = 0; e < 300_000; e++) {
if (!Thread.currentThread().isInterrupted()) {
Math.random();Math.random();Math.random();
System.out.println("MyCallableB2 " + (e + 1));
} else {
System.out.println(" 抛异常了,中断了!");
throw new InterruptedException("中断了");
}
}
System.out.println("MyCallableB2 ends " + System.currentTimeMillis());
return "return B2";
}
}
2、invokeAny(Collection tasks) 与 执行慢的任务异常
假设 tasks 里有t1 、 t2两个任务:
-
a. 设 t1 快,t2 慢,t1正常,t2异常。默认不会在主线程输出异常,要显式在t2中抓异常。即使在t2 中显式地抓住了异常,该异常仍然不会影响到主线程的正常执行,言下之意是,主线程捕捉不到子线程抛出的异常(demo 7.2.2 & demo 7.2.3)
-
b. 设t1 快,t2 慢,t1 异常,t2 正常,则t1 的异常仍然不能显示到控制台,而需要显式try catch,同时会等待慢的任务t2返回结果值。 假如t1 中显式 try catch 了后没有重新将这个异常抛出,则仍将返回 t1 的结果,因为在主线程看来,并没有收到 t1 异常的消息(这个异常被吞掉了)。
注:先出现异常,但不影响后面任务取值的原理在于源码中一直判断(死循环)有无正确的返回值,若到最后都无返回值则抛出异常,这个异常就是最后出现的异常。参见:AbstractExcecutorService method: doInvokeAny()
//todo 源码分析 -
c. 设tasks 里有三个任务A、B、C,设A B C 一起执行,都有异常,最终的异常就是最后一个出现的异常。(demo 7.2.4)
总结一下:tasks
任务中假如有正常的,则返回最后一个正常任务的结果;
假如都是异常的,则主线程也会抛出异常。
假如任务中try catch
中显式地 捕获了异常,却没有再次往上抛,则该任务对主线程来说是正常的(相当于异常被“吞掉”了)。
// demo 7.2.2
class MyExecutorService3{
public static void main(String[] args) {
Callable<String> callable1 = () -> {
System.out.println("callable1 " + Thread.currentThread().getName() +
" begins " + System.currentTimeMillis());
IntStream.range(0, 1_000).forEach(e -> {
Math.random();Math.random();Math.random();
System.out.println(" callable1 running " + (e + 1));
});
System.out.println("callable1 " + Thread.currentThread().getName() +
" ends " + System.currentTimeMillis());
return " callable1";
};
Callable<String> callable2 = () -> {
System.out.println("callable2 " + Thread.currentThread().getName() +
" begins " + System.currentTimeMillis());
IntStream.range(0, 1_0000).forEach(e -> {
Math.random();Math.random();Math.random();Math.random();
System.out.println(" callable2 running " + (e + 1));
});
if (true) {
// 此处抛出的NPE不会在主线程打印出来。必须显式地 try catch,才能捕捉到异常,打印到控制台
System.out.println("===开始中断 callable2 ====");
throw new NullPointerException("被抛出的NPE");
}
System.out.println("callable2 " + Thread.currentThread().getName() +
" ends " + System.currentTimeMillis());
return " callable2 ";
};
ArrayList<Callable<String>> callables = Lists.newArrayList(callable1, callable2);
ExecutorService executorService = Executors.newFixedThreadPool(10);
try {
String any = executorService.invokeAny(callables);
System.out.println(" 获取 执行结果值: " + any);
System.out.println(" 主线程继续执行 ");
} catch (InterruptedException e) {
e.printStackTrace();
System.out.println(" main 1 ");
} catch (ExecutionException e) {
e.printStackTrace();
System.out.println(" main 2 ");
}
}
}
// demo 7.2.3
class MyExecutorService4{
public static void main(String[] args) {
Callable<String> callable1 = () -> {
System.out.println("callable1 " + Thread.currentThread().getName() +
" begins " + System.currentTimeMillis());
IntStream.range(0, 1_000).forEach(e -> {
Math.random();Math.random();Math.random();
System.out.println(" callable1 running " + (e + 1));
});
System.out.println("callable1 " + Thread.currentThread().getName() +
" ends " + System.currentTimeMillis());
return " callable1";
};
// 显式 try catch ,将错误捕捉到
Callable<String> callable2 = () -> {
try {
System.out.println("callable2 " + Thread.currentThread().getName() +
" begins " + System.currentTimeMillis());
IntStream.range(0, 1_0000).forEach(e -> {
Math.random();Math.random();Math.random();Math.random();
System.out.println(" callable2 running " + (e + 1));
});
if (true) {
// 此处抛出的NPE不会在主线程打印出来。必须显式地 try catch,才能捕捉到异常,打印到控制台
System.out.println("===开始中断 callable2 ====");
throw new NullPointerException("被抛出的NPE");
}
System.out.println("callable2 " + Thread.currentThread().getName() +
" ends " + System.currentTimeMillis());
} catch (NullPointerException e) {
e.printStackTrace();
// =======捕捉到 异常之后,再主动抛出去=====
throw e;
}
return " callable2 ";
};
ArrayList<Callable<String>> callables = Lists.newArrayList(callable1, callable2);
ExecutorService executorService = Executors.newFixedThreadPool(10);
try {
String any = executorService.invokeAny(callables);
System.out.println(" 获取 执行结果值: " + any);
System.out.println(" 主线程继续执行 ");
// ====主线程里无法 catch 到 子线程抛出的异常(线程间不直接通信)
} catch (InterruptedException e) {
e.printStackTrace();
System.out.println(" main 1 ");
} catch (ExecutionException e) {
e.printStackTrace();
System.out.println(" main 2 ");
} catch (Exception e) {
e.printStackTrace();
System.out.println(" main 3 ");
}
}
}
//demo :7.2.4
class MyExecutorService5 {
public static void main(String[] args) {
// 快
Callable<String> callable1 = () -> {
System.out.println("callable1 " + Thread.currentThread().getName() +
" begins " + System.currentTimeMillis());
IntStream.range(0, 1_000).forEach(e -> {
Math.random();Math.random();Math.random();
System.out.println(" callable1 running " + (e + 1));
});
if (true) {
System.out.println(" ==== 开始中断 callable1 === ");
throw new RuntimeException("被抛出的NPE callable1 ");
}
System.out.println("callable1 " + Thread.currentThread().getName() +
" ends " + System.currentTimeMillis());
return " callable1";
};
// 慢
Callable<String> callable2 = () -> {
System.out.println("callable2 " + Thread.currentThread().getName() +
" begins " + System.currentTimeMillis());
IntStream.range(0, 1_0000).forEach(e -> {
Math.random();Math.random();Math.random();Math.random();
System.out.println(" callable2 running " + (e + 1));
});
if (true) {
// 此处抛出的NPE不会在主线程打印出来。必须显式地 try catch,才能捕捉到异常,打印到控制台
System.out.println("===开始中断 callable2 ====");
throw new NullPointerException("被抛出的NPE callable2 ");
}
System.out.println("callable2 " + Thread.currentThread().getName() +
" ends " + System.currentTimeMillis());
return " callable2 ";
};
ArrayList<Callable<String>> callables = Lists.newArrayList(callable1, callable2);
ExecutorService executorService = Executors.newFixedThreadPool(10);
try {
// 由于两个 子任务都抛出了异常,主线程实际上也会抛异常
String any = executorService.invokeAny(callables);
System.out.println(" 获取 执行结果值: " + any);
System.out.println(" 主线程继续执行 ");
} catch (InterruptedException e) {
e.printStackTrace();
System.out.println(" main 1 ");
} catch (ExecutionException e) {
e.printStackTrace();
// 进入了这个方法块!主线程 捕捉到了 子线程的异常!
System.out.println(" main 2 ");
} catch (Exception e) {
e.printStackTrace();
System.out.println(" main 3 ");
}
}
}
3、invokeAny(Collection tasks, timeout ,timeUnits)
在指定的时间内取得第一个先执行完任务的结果值。假如在指定时间内没能执行完任务,将会抛出异常。超时时,执行中的任务状态会变成“interrupted”,所以可以结合
if(Thread.current().isInterrupted) {
throw new InterruptionExceptioin()
}
来使运行中的线程通过抛异常中断。
再提出一个具体的场景:假如一个任务在执行时既出现了异常,又超时了,会出现什么现象呢?
答曰:主线程超时后会抛出异常,中断主线程的继续执行,所以不能获取任务的执行结果;主线程抛出异常后,子任务会在执行完毕后被interrupt。不过需要注意的是,如果希望记录子线程的异常,需要主动try catch ,若只是简单throw 出去,是打印不了异常栈轨的。//demo 7.2.5
// ===================下面都是代码demo====================
// demo 7.2.5
class MyExecutorService6{
public static void main(String[] args) {
Callable<String> callable1 = () -> {
System.out.println("callable1 " + Thread.currentThread().getName() +
" begins " + System.currentTimeMillis());
IntStream.range(0, 1_234).forEach(e -> {
Math.random();Math.random();Math.random();Math.random();Math.random();
System.out.println(" callable1 running " + (e + 1));
});
if (true) {
System.out.println(" ==== 开始中断 callable1 === ");
// 注: 假如此处不使用 try catch 抓住异常,那么抛出的异常是不会在控制台显示的
throw new NullPointerException("被抛出的NPE callable1 ");
}
System.out.println("callable1 " + Thread.currentThread().getName() +
" ends " + System.currentTimeMillis());
return " callable1";
};
ExecutorService executorService = Executors.newFixedThreadPool(10);
try {
//
String any = executorService.invokeAny(Lists.newArrayList(callable1), 10 , TimeUnit.MILLISECONDS);
System.out.println("获取 任务结果" + any);
System.out.println("---------");
} catch (InterruptedException e) {
e.printStackTrace();
System.out.println(" main A ");
} catch (ExecutionException e) {
e.printStackTrace();
System.out.println(" main B ");
} catch (TimeoutException e) {
// ======主线程会进入本代码块,表示 主线程已经抛出了异常,try 中的任务结果不能再获取了=====
e.printStackTrace();
System.out.println("main C");
}
}
}
7.3 invokeAll
7.3.1 List<Future< T>> invokeAll(Collection tasks)
这是最简单的情形,调用方将会阻塞在invokeAll
这一行,直到所有的tasks都执行完,或者抛出异常,见 demo 7.3.1
方法API:
// demo 7.3.1
class MyExecutorService7{
public static void main(String[] args) {
Callable<String> callableA = () -> {
System.out.println(Thread.currentThread().getName() + " begin " + DateUtil.currentTime());
TimeUnit.SECONDS.sleep(5);
System.out.println(Thread.currentThread().getName() + " end " + DateUtil.currentTime());
return " return A";
};
Callable<String> callableB = () -> {
System.out.println(Thread.currentThread().getName() + " begin " + DateUtil.currentTime());
TimeUnit.SECONDS.sleep(8);
System.out.println(Thread.currentThread().getName() + " end " + DateUtil.currentTime());
return " return B";
};
ExecutorService executorService = Executors.newFixedThreadPool(10);
try {
// invokeAll 会阻塞,直到tasks中的最后一个任务执行完,或者抛出异常
System.out.println(" 开始执行时间 "+ DateUtil.currentTime());
List<Future<String>> futures = executorService.invokeAll(Lists.newArrayList(callableA, callableB));
System.out.println(" 执行完时间 "+ DateUtil.currentTime());
for (Future<String> future : futures) {
// 实际在get() 的时候就可能抛出 ExecutionException
// get() 会阻塞
String getStr = future.get();
System.out.println(" 返回结果 " + getStr);
}
} catch (InterruptedException e) {
e.printStackTrace();
System.out.println(" main A ");
} catch (ExecutionException e) {
e.printStackTrace();
System.out.println(" main B");
}
}
}
7.3.2 List<Future< T> invokeAll(Collection tasks)中tasks的异常处理
假如不从invokeAll()
中获取结果,即不执行future.get()
方法,那么结果tasks中抛出异常也不影响主线程;
假如需要从invokeAll()
中获取结果,则tasks中任务的顺序可能影响主线程的执行流程。
请看demo 7.3.2。
小伙伴们应该能看明白:
异常也是一种任务执行结果,跟正常的执行结果一样,假如主线程不主动从Future中获取执行结果,那么任务的异常是不会传递到主线程中的。
// demo 7.3.2
class MyExecutorService9 {
public static void main(String[] args) {
Callable<String> callableA = () -> {
System.out.println(" callable A begins" + DateUtil.currentTime());
// 此处抛出异常
int i = 1 / 0;
System.out.println("' callable A ends " + DateUtil.currentTime());
return " callable A";
};
Callable<String> callableB = () -> {
System.out.println(" callable B begins" + DateUtil.currentTime());
IntStream.range(0, 100).forEach(u -> {
Math.random();
Math.random();
Math.random();
});
System.out.println("' callable B ends " + DateUtil.currentTime());
return " callable B";
};
ExecutorService executorService = Executors.newFixedThreadPool(10);
try {
// 任务顺序影响 后面for()循环中获取执行结果
// List<Future<String>> futures = executorService.invokeAll(Lists.newArrayList(callableA, callableB));
List<Future<String>> futures = executorService.invokeAll(Lists.newArrayList(callableB, callableA));
for (Future<String> future : futures) {
// 注意: 实际的异常是从 get() 抛出来的
System.out.println(future.get());
}
} catch (InterruptedException e) {
System.out.println(" caught exception in main 1 ");
e.printStackTrace();
} catch (ExecutionException e) {
System.out.println(" caught exception in main 2 ");
} finally {
executorService.shutdown();
}
}
}
7.3.3 List< Future < T>>invokeAll(Collection tasks, timeout, timeUnit)
该API作用,如其传参所示,就是在指定时间内获取任务执行结果的。假如在指定时间里未能完成所有任务,调用Future.get()方法 将会抛出CancellatioinException异常。注意这点和invokeAny()
抛出来的TimeoutException倒是不同。
其实我们直觉上去理解:
invokeAll()
的返回值其实是封装了多个Future
(执行结果:包括正常结果+异常),假如在执行时间内没有完成所有任务,那么未完成的任务实际上相当于被“取消(cancel)”了,所以通过Future.get()
会抛出 CancellationException`;invokeAny(
)只会返回一个结果,若在指定时间内没有返回,说明所有的任务都没完成,所以会抛出TimeoutException
。
7.4 总结
ExecutorService
定义了 管理Future
异步任务的接口,我们常说的“线程池对象”其实就是ExecutorService
的基本实现。ExecutorService
定义的invokeAny \ invokeAll
方法规定了如何获取一组任务 的结果。
更多推荐
所有评论(0)