用过多线程的人肯定都知道,最常用的方式就是通过runnable接口,然后通过Thread类或者线程池来异步跑起来。
但是这个接口有个很大的缺陷:它其中的抽象方法没有返回值。想要拿到异步的处理结果,只能通过引用一个对象,或者一个全局变量,来查看结果,并且由于无法获知线程是否已经执行完毕,只能不断的去取值判断,很不优雅。
java5的版本中,为了解决这个问题,提供了两个新接口Callable/future。
callable源码如下
1 @FunctionalInterface
2 public interface Callable<V> {
3 /**
4 * Computes a result, or throws an exception if unable to do so.
5 *
6 * @return computed result
7 * @throws Exception if unable to compute a result
8 */
9 V call() throws Exception;
10 }
接口很简单,提供了一个call 方法,类似于runnable 中的run方法一样,用来定义异步任务的业务逻辑。
但注意,它有两个很重要的区别:
(1)有泛型的返回值
(2)接口有异常抛出
注意,runnable是不能抛出受检异常的,这相当于为异步任务的结束扩充了途径。
1 @FunctionalInterface
2 public interface Runnable {
3 /**
4 * When an object implementing interface {@code Runnable} is used
5 * to create a thread, starting the thread causes the object's
6 * {@code run} method to be called in that separately executing
7 * thread.
8 * <p>
9 * The general contract of the method {@code run} is that it may
10 * take any action whatsoever.
11 *
12 * @see java.lang.Thread#run()
13 */
14 public abstract void run();
15 }
接着是 future接口,future的主要作用是获取返回值。它的本质是一个句柄,是拿到返回值的一个"凭证"。
说到句柄,这个翻译实在是太粗糙、晦涩了。我实在无法理解当时的翻译人员是怎么想的,先不说信达雅,连汉语是母语的人都一脸懵。
这里简单解释下,所谓的句柄就是间接引用。
句柄的英文是handle,表示 把手、手柄、握把。(防盗连接:本文首发自http://www.cnblogs.com/jilodream/ )比如门把手,水杯把手。是持有对象的一个间接引用。表示你可以间接用手(hanle)触及到的东西。
这里我们可以粗糙的理解future就是返回结果的一个"把手",通过它来间接的控制返回结果。
来看一下java中的Future接口的源码:
1 public interface Future<V> {
2
3 boolean cancel(boolean mayInterruptIfRunning);
4
5
6 boolean isCancelled();
7
8
9 boolean isDone();
10
11
12 V get() throws InterruptedException, ExecutionException;
13
14
15 V get(long timeout, TimeUnit unit)
16 throws InterruptedException, ExecutionException, TimeoutException;
17 }
get() 方法是获取返回结果,因为是异步等待,并不知任务的具体的返回结果和时间,所以是get方法阻塞式的等待方法。
它有两个重载,一个是无限制等待,一个是有超时时间的等待。这两个重载方法会抛出多个异常:
(1)InterruptedException
阻塞方法一般会声明抛出InterruptedException来中断线程,防止无限制等待。(不了解中断线程的同学可以看看我之前的文章 *线程的中断 https://www.cnblogs.com/jilodream/p/22869617*)
(2)ExecutionException
call方法本身是支持抛出异常的,当异常抛出后,就可以在future的get方法处,接收到执行异常ExecutionException。
这个异常是对call方法抛出的异常进行的封装。我们在获取返回结果时,应该考虑到对这个异常的处理。
(3)TimeoutException
超时异常是只,对于设置超时时间等待的get方法,如果等待时间到,则会抛出该异常来终止等待。
(4)CancellationException
CancellationException是运行时异常,(防盗连接:本文首发自http://www.cnblogs.com/jilodream/ )所以没有显示的方法声明时标记。
这个异常表示当前异步任务已经被取消了,无法获取到返回结果。无论是get前任务被取消,还是get过程中任务被取消。get方法都会抛出此异常。
接下来先说cancel方法()
cancel(boolean mayInterruptIfRunning)
方法表示要取消异步任务,但是这个取消会考虑到任务的状态。
传false,表示对于开始运行的异步任务,不会调用线程中断interrupt。
传true,表示对于开始运行的异步任务,会调用线程中断interrupt。
注意无论任务是否已经开始启动,只要任务还没有完成,该方法都会将任务状态置为已取消。
因为任务线程一旦开始,如果被终止的话,可能会有资源泄露,连接未关闭等问题。所以这里一般要谨慎考虑。
而所谓的取消,并不是调用thread的stop之类的方法来强制取消,也是通过中断线程,来尝试取消。尝试取消一般是通过更改任务状态,调用线程的interrupt来中断。如果都没问题,cancel就会返回true。但是假若任务线程内部并没有中断方法,或相应的中断check,仍然无法处理中断,还会无止尽的。也就是说cancel虽然返回true了,但是其实任务线程还是会无休止的运行下去。
而任务如果已经完成了,或者已经被取消了。修改状态失败,则该方法会返回false。
然后是boolean isCancelled()方法,该方法很简单,用来判断当前任务是否已经被取消了。
最后是**isDone(),**用来获取异步任务是否已经结束(包括完成或者是取消)。
Future接口是一个泛型接口,需要和配合使用的Callable的输出泛型保持一致。
这两接口常常是配合线程池来使用。我们举个例子:
1 import java.util.concurrent.Callable;
2 import java.util.concurrent.ExecutionException;
3 import java.util.concurrent.ExecutorService;
4 import java.util.concurrent.Executors;
5 import java.util.concurrent.Future;
6 import java.util.concurrent.TimeUnit;
7
8
9 public class CallableStudy {
10 public static void main(String[] args) throws ExecutionException, InterruptedException {
11 Callable<String> task1 = new Callable<String>() {
12 @Override
13 public String call() throws InterruptedException {
14 TimeUnit.SECONDS.sleep(1L);
15 return "hi , 很高兴遇见你";
16 }
17 };
18
19 Callable<String> task2 = new Callable<String>() {
20 @Override
21 public String call() throws InterruptedException {
22 TimeUnit.SECONDS.sleep(1L);
23 return "hi , 很高兴再次遇见你";
24 }
25 };
26
27 ExecutorService executor = Executors.newSingleThreadExecutor();
28 Future<String> future1 = executor.submit(task1);
29 Future<String> future2 = executor.submit(task2);
30 String result = future1.get();
31 System.out.println("call1 ,result :"+result + " status :"+ future1.isDone()+","+future2.isDone());
32 result = future2.get();
33 System.out.println("call2 ,result :"+result + " status :"+ future1.isDone()+","+future2.isDone());
34 }
35 }
输出如下
call1 ,result :hi , 很高兴遇见你 status :true,false
call2 ,result :hi , 很高兴再次遇见你 status :true,true
call1刚拿到返回值时,call1任务是已完成,call2任务肯定在运行(睡眠中),所以call2此时isDone是false
再来看下cancel的场景
1 package org.example.webdevelop;
2
3 import java.util.concurrent.Callable;
4 import java.util.concurrent.ExecutionException;
5 import java.util.concurrent.ExecutorService;
6 import java.util.concurrent.Executors;
7 import java.util.concurrent.Future;
8 import java.util.concurrent.TimeUnit;
9
10 public class CallableCancelStudy {
11 public static void main(String[] args) throws ExecutionException, InterruptedException {
12 Callable<String> task1 = new Callable<String>() {
13 @Override
14 public String call() throws InterruptedException {
15 System.out.println("start call 1");
16 TimeUnit.SECONDS.sleep(1L);
17 System.out.println("end call 1");
18 return "hi , 很高兴遇见你";
19 }
20 };
21
22 Callable<String> task2 = new Callable<String>() {
23 @Override
24 public String call() throws InterruptedException {
25 System.out.println("start call 2");
26 TimeUnit.SECONDS.sleep(1L);
27 System.out.println("end call 2");
28 return "hi , 很高兴再次遇见你";
29 }
30 };
31
32 Callable<String> task3 = new Callable<String>() {
33 @Override
34 public String call() throws InterruptedException {
35 System.out.println("start call 3");
36
37 System.out.println("end call 3");
38 return "hi , 很高兴再次遇见你";
39 }
40 };
41
42 ExecutorService executor = Executors.newSingleThreadExecutor();
43 Future<String> future3 = executor.submit(task3);
44 Future<String> future1 = executor.submit(task1);
45 Future<String> future2 = executor.submit(task2);
46 TimeUnit.MILLISECONDS.sleep(500);
47 future3.get();
48 boolean cancelOpt1 = future1.cancel(false);
49 boolean cancelOpt2 = future2.cancel(false);
50 boolean cancelOpt3 = future3.cancel(false);
51
52 System.out.println("call's cancel result :" + cancelOpt1 + "," + cancelOpt2 + "," + cancelOpt3);
53 System.out.println("call's done status :" + future1.isDone() + "," + future2.isDone() + "," + future3.isDone());
54 System.out.println("call1 ,cancel status :" + future1.isCancelled() + "," + future2.isCancelled() +
55 "," + future3.isCancelled());
56 }
57 }
输出如下:
start call 3
end call 3start call 1call's cancel result :true,true,false
call's done status :true,true,true
call1 ,cancel status :true,true,false
end call 1
开始执行取消操作时,call1,运行中,call2 还未开始启动(因为线程池只有1个线程),call3 已经任务执行完成
我们逐一分析输出日志,(防盗连接:本文首发自http://www.cnblogs.com/jilodream/ )我们可以得到三个信息:
(1)此时的三个取消动作的结果为(红色日志),成功,成功,失败。即对已经运行完成的任务是尝试取消失败的。
(2)无论取消是否成功,取消结束后,完成状态isDone就都为true了(绿色日志)。这是由于任务运行完成的,虽然标记失败,但是它本来就是isDone=true的效果。任务未完成的(执行中+未开始),都会因为被标记为取消,而将完成状态isDone 标记为true
(3)任务1虽然已经被取消成功了(蓝色日志),任务1的取消状态也已改变,但是由于没有收到中断信号interrupt,因此还是会一直运行直到结束。