5:Future和Callable的使用
在JDK1.5的并发包可以使用Future和Callable来使线程具有返回值的功能。
Callable与Runnable的主要区别:
- Callable接口的call()方法可以有返回值,而Runnable的run()没有。
- call()方法可以声明抛出异常,而run()方法不能。
5.1.:Callable的简单使用
5.2: get()和sumbit()方法的使用
1、 get()方法:
- get()方法是具有阻塞特性的,它可以结合future中的isDone()方法来搭配使用,isDone()是判断future中的线程是否完成。
- get(long timeout,TimeUnit unit): 在指定的时间内等待获得返回值,如果在指定时间内没有获取到则抛出异常。
2、submit()方法:
- submit不仅可以传入Callable还可以传入Runnable,说明它支持有返回值和无返回值的功能,如果没有返回值的话它输出null。
- submit(Runnable,T result): 第二个参数result可以作为执行结果的返回值,而不需要使用get()方法来获取,即将result作为默认返回值返回。
- 与execute()的区别: execute()没有返回值,默认遇到异常直接抛出,但可以通过自定义线程工厂来捕获,而submit()可以使用try/catch来捕获异常。
5.3:其他API的使用
- cancel(boolean mayInterruptIfRunning): 参数的作用是如果线程正在运行是否中断,在代码中需要使用if(Thread.currentThread().isInterrupted()==true)进行搭配。返回值是发送取消命令是否成功完成(而不是线程是否中断成功),如果线程本身已经结束了,在发送cancel来取消线程,则返回false。
- isCancelled():线程是否取消。
- 它可以与之前的线程池那样可以定制线程工厂与自定义拒绝策略。
5.4:Future的缺点
调用get()方法时是阻塞的,一旦前面先行的任务耗时很多,后面的任务调用get()则一直要阻塞,大大影响效率。
6:CompletionService的使用
它的功能是以异步的方式一边生产新的任务,一边处理已完成的任务的结果。使用sumbit提交任务,使用take取得已完成的任务,并按照完成的时间顺序处理结果。
它的实现结构:
6.1:CompletionService的常用方法:
常用方法解析:
方法Poll(long timeout ,TimeUnit unit)的实验:
6.2:CompletionService与异常
第一种情况:不使用get方法,不出现异常
第二种:使用get,B出现异常,A顺利执行。
第三种如果执行顺序为先执行B,再执行A,则他们的结果都不能输出。
7:ExecutorService的使用
7.1.1: invokeAny的使用介绍
补充: 该方法具有阻塞特性,它会等待取得第一个完成任务的值,当第一个任务执行后,会调用interrupt()来将其他任务打断,其他任务可以结合if(Thread.currentThread().isInterrupted()==true)代码来决定线程是否继续执行下去。如果再上面if判断内的代码,使用的是throws new InterruptException来中断线程,虽然异常成功抛出,但是在main线程中是不会捕获它的。要想捕获异常,需在Callable中使用try/catch来进行捕获。
7.1.2:invokeAny与异常
假设A是执行较快的线程,B是执行较慢的线程
1、B出现异常: 主线程成功取得A的返回值,B出现异常,默认情况下不会在控制台打印异常信息,如果使用try/catch捕获异常则可以打印异常信息。
2、A出现异常: 默认不打印异常信息,等待B任务完成。A出现异常不影响B任务的取值,原理是源代码中一直在判断有没有正确的返回值。如果A任务捕获了异常,但没有用throws重新抛出,则主线程不会取得B结果值,因为A没有向主线程上报异常信息,主线程认为A是正确的,所以并不会把关注点放在B上。
3、A,B都出现异常: 最终的异常出现在B上。
总结:使用invokeAny方法的某一个任务正确执行,则其他Callable抛出的异常并不处理。 都没有正确返回值,则处理最后被抛出的异常。
7.1.3:异常情况的验证
B出现异常
A出现异常
A,B都出现异常
A出现异常捕获了,但未上报,B不出现异常
使用catch捕获了异常,有了打印信息,但是没有再次抛出,使得主线程认为A的返回结果是正确的,使用了A的结果。
如果将A中注释的抛出异常代码正常执行得到的结果是:
7.2.invokeAll方法的使用
7.2.1:invokeAll方法介绍
它会返回所有任务的执行结果,并且具有阻塞特性,要把所有结果都取回才继续向下执行。它与invokeAny并一样,它可以捕获Callable抛出的异常。
7.2.2:invokeAll与异常
invokeAll异常情况与快慢并无太大的关系,因为它返回的是个Future数组,如果你在得到Future数组之后,并未使用get方法去取任务的返回值,则主线程不会出现异常。而出现了异常则说明你在此时使用了返回结果不正确的Future的get()方法。
例如:线程A出现异常(Callable代码跟上一节一样)
7.2.3 invoke方法加入超时参数
invokeAll(Collection<? extends Callable> tasks, long timeout, TimeUnit unit)作用是如果全部任务没有在指定时间内完成,则出现异常。
在上节代码的基础上仅修改invoke方法:
结果没有发生改变,说明即使是错误的返回结果,invoke方法仍认为它是成功返回的。
一个超时,一个不超时打印结果
将A出现异常的代码注释掉