java多线程学习 基础篇(四) Callable、Future和FutureTask浅析


通过之前的学习,我们知道创建线程的方式有两种,一种是实现Runnable接口,另一种是继承Thread,但是这两种方式都有个缺点,那就是在任务执行完成之后无法获取返回结果,那如果我们想要获取返回结果该如何实现呢?

从JAVA SE 5.0开始引入了Callable和Future,通过它们构建的线程,在任务执行完成后就可以获取执行结果,这也是所谓的“异步”模型。

1、Callable接口

我们先回顾一下java.lang.Runnable接口,就声明了run(),其返回值为void,当然就无法获取结果了。
1 public interface Runnable {
2     public abstract void run();
3 }

而Callable的接口定义如下

 1 @FunctionalInterface
 2 public interface Callable {
 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 }
可以看到Callable是一个函数式接口。同时Callable是有返回值的,并且支持泛型。   论是Runnable接口的实现类还是Callable接口的实现类,都可以被ThreadPoolExecutor或ScheduledThreadPoolExecutor执行, ThreadPoolExecutor或ScheduledThreadPoolExecutor都实现了 ExcutorService接口, 而因此 Callable需要和Executor框架中的ExcutorService结合使用,我们先看看ExecutorService提供的方法:   
1  Future submit(Callable task);
2  Future submit(Runnable task, T result);
3 Future<?> submit(Runnable task);
第一个方法:submit提交一个实现Callable接口的任务,并且返回封装了异步计算结果的Future。 第二个方法:submit提交一个实现Runnable接口的任务,并且指定了在调用Future的get方法时返回的result对象。 第三个方法:submit提交一个实现Runnable接口的任务,并且返回封装了异步计算结果的Future。 因此我们只要创建好我们的线程对象(实现Callable接口或者Runnable接口),然后通过上面3个方法提交给线程池去执行即可。 还有点要注意的是,除了我们自己实现Callable对象外,我们还可以使用工厂类Executors来把一个Runnable对象包装成Callable对象。Executors工厂类提供的方法如下:
1 public static Callable callable(Runnable task)
2 public static  Callable callable(Runnable task, T result)


2、Future接口

Future接口是用来获取异步计算结果的,说白了就是对具体的Runnable或者Callable对象任务执行的结果进行获取(get()),取消(cancel()),判断是否完成等操作。我们看看Future接口的源码:
 1 public interface Future {
 2 
 3     /**
 4      * Attempts to cancel execution of this task.  This attempt will
 5      * fail if the task has already completed, has already been cancelled,
 6      * or could not be cancelled for some other reason. If successful,
 7      * and this task has not started when {@code cancel} is called,
 8      * this task should never run.  If the task has already started,
 9      * then the {@code mayInterruptIfRunning} parameter determines
10      * whether the thread executing this task should be interrupted in
11      * an attempt to stop the task.
12      *
13      * 

After this method returns, subsequent calls to {@link #isDone} will 14 * always return {@code true}. Subsequent calls to {@link #isCancelled} 15 * will always return {@code true} if this method returned {@code true}. 16 * 17 * @param mayInterruptIfRunning {@code true} if the thread executing this 18 * task should be interrupted; otherwise, in-progress tasks are allowed 19 * to complete 20 * @return {@code false} if the task could not be cancelled, 21 * typically because it has already completed normally; 22 * {@code true} otherwise 23 */ 24 boolean cancel(boolean mayInterruptIfRunning); 25 26 /** 27 * Returns {@code true} if this task was cancelled before it completed 28 * normally. 29 * 30 * @return {@code true} if this task was cancelled before it completed 31 */ 32 boolean isCancelled(); 33 34 /** 35 * Returns {@code true} if this task completed. 36 * 37 * Completion may be due to normal termination, an exception, or 38 * cancellation -- in all of these cases, this method will return 39 * {@code true}. 40 * 41 * @return {@code true} if this task completed 42 */ 43 boolean isDone(); 44 45 /** 46 * Waits if necessary for the computation to complete, and then 47 * retrieves its result. 48 * 49 * @return the computed result 50 * @throws CancellationException if the computation was cancelled 51 * @throws ExecutionException if the computation threw an 52 * exception 53 * @throws InterruptedException if the current thread was interrupted 54 * while waiting 55 */ 56 V get() throws InterruptedException, ExecutionException; 57 58 /** 59 * Waits if necessary for at most the given time for the computation 60 * to complete, and then retrieves its result, if available. 61 * 62 * @param timeout the maximum time to wait 63 * @param unit the time unit of the timeout argument 64 * @return the computed result 65 * @throws CancellationException if the computation was cancelled 66 * @throws ExecutionException if the computation threw an 67 * exception 68 * @throws InterruptedException if the current thread was interrupted 69 * while waiting 70 * @throws TimeoutException if the wait timed out 71 */ 72 V get(long timeout, TimeUnit unit) 73 throws InterruptedException, ExecutionException, TimeoutException; 74 }

  方法解析: V get() :   获取异步执行的结果,如果没有结果可用,此方法会阻塞直到异步计算完成。 V get(Long timeout , TimeUnit unit) :   获取异步执行结果,如果没有结果可用,此方法会阻塞,但是会有时间限制,如果阻塞时间超过设定的timeout时间,该方法将抛出异常。 boolean isDone() :   如果任务执行结束,无论是正常结束或是中途取消还是发生异常,都返回true。 boolean isCanceller() :   如果任务完成前被取消,则返回true。 boolean cancel(boolean mayInterruptRunning) :   如果任务还没开始,执行cancel(...)方法将返回false;如果任务已经启动,执行cancel(true)方法将以中断执行此任务线程的方式来试图停止任务,如果停止成功,返回true;当任务已经启动,执行cancel(false)方法将不会对正在执行的任务线程产生影响(让线程正常执行到完成),此时返回false;当任务已经完成,执行cancel(...)方法将返回false。mayInterruptRunning参数表示是否中断执行中的线程。   通过方法分析我们也知道实际上Future提供了3种功能: (1)能够中断执行中的任务 (2)判断任务是否执行完成 (3)获取任务执行完成后额结果。 但是我们必须明白Future只是一个接口,我们无法直接创建对象,因此就需要其实现类FutureTask登场啦。    

3、FutureTask类

我们先来看看FutureTask的实现
1 public class FutureTask implements RunnableFuture { }
FutureTask类实现了RunnableFuture接口,我们看一下RunnableFuture接口的实现:
 1 /**
 2  * A {@link Future} that is {@link Runnable}. Successful execution of
 3  * the {@code run} method causes completion of the {@code Future}
 4  * and allows access to its results.
 5  * @see FutureTask
 6  * @see Executor
 7  * @since 1.6
 8  * @author Doug Lea
 9  * @param  The result type returned by this Future's {@code get} method
10  */
11 public interface RunnableFuture extends Runnable, Future {
12     /**
13      * Sets this Future to the result of its computation
14      * unless it has been cancelled.
15      */
16     void run();
17 }

分析:

FutureTask除了实现了Future接口外还实现了Runnable接口,因此FutureTask也可以直接提交给Executor执行。 当然也可以调用线程直接执行(FutureTask.run())。接下来我们根据FutureTask.run()的执行时机来分析其所处的3种状态:

(1)未启动,FutureTask.run()方法还没有被执行之前,FutureTask处于未启动状态,当创建一个FutureTask,而且没有执行FutureTask.run()方法前,这个FutureTask也处于未启动状态。 (2)已启动,FutureTask.run()被执行的过程中,FutureTask处于已启动状态。 (3)已完成,FutureTask.run()方法执行完正常结束,或者被取消或者抛出异常而结束,FutureTask都处于完成状态。

下面我们再来看看FutureTask的方法执行示意图

分析: (1)当FutureTask处于未启动或已启动状态时,如果此时我们执行FutureTask.get()方法将导致调用线程阻塞; (2)当FutureTask处于已完成状态时,执行FutureTask.get()方法将导致调用线程立即返回结果或者抛出异常。 (3)当FutureTask处于未启动状态时,执行FutureTask.cancel()方法将导致此任务永远不会执行。 (4)当FutureTask处于已启动状态时,执行cancel(true)方法将以中断执行此任务线程的方式来试图停止任务,如果任务取消成功,cancel(...)返回true;     但如果执行cancel(false)方法将不会对正在执行的任务线程产生影响(让线程正常执行到完成),此时cancel(...)返回false。 (5)当任务已经完成,执行cancel(...)方法将返回false。   最后,给出FutureTask的两种构造函数:
1 public FutureTask(Callable callable) {
2 }
3 public FutureTask(Runnable runnable, V result) {
4 }

4、Callable/Future/FutureTask的使用

通过上面的介绍,我们对Callable,Future,FutureTask都有了比较清晰的了解了,那么它们到底有什么用呢?我们前面说过通过这样的方式去创建线程的话,最大的好处就是能够返回结果。 假设有这样的场景,我们现在需要计算一个数据,而这个数据的计算比较耗时,而我们后面的程序也要用到这个数据结果,那么这个时 Callable岂不是最好的选择?我们可以开设一个线程去执行计算,而主线程继续做其他事,而后面需要使用到这个数据时,我们再使用Future获取不就可以了吗?下面我们就来编写一个这样的实例

4.1 使用Callable+Future获取执行结果

Task实现类如下:

 1 public class CallableDemo implements Callable {
 2     private int sum = 0;
 3 
 4     @Override
 5     public Integer call() throws Exception {
 6         System.out.println("Callable子线程开始计算啦!");
 7         Thread.sleep(2000);
 8 
 9         for (int i = 0; i < 5000; i++) {
10             sum = sum + i;
11         }
12         System.out.println("Callable子线程计算结束!");
13         return sum;
14     }
15 
16     public static void main(String[] args) {
17         CallableTest callableTest = new CallableTest();
18         callableTest.test01();
19     }
20 }

测试类如下:

 1 class CallableTest{
 2     void test01(){
 3         //创建线程池
 4         ExecutorService es = Executors.newSingleThreadExecutor();
 5         //创建Callable对象任务
 6         CallableDemo calCallableDemo =new CallableDemo();
 7         //提交任务并获取执行结果
 8         Future future =es.submit(calCallableDemo);
 9         //关闭线程池
10         es.shutdown();
11         try {
12             Thread.sleep(2000);
13             System.out.println("主线程在执行其他任务");
14 
15             if(future.get()!=null){
16                 //输出获取到的结果
17                 System.out.println("future.get()-->"+future.get());
18             }else{
19                 //输出获取到的结果
20                 System.out.println("future.get()未获取到结果");
21             }
22 
23         } catch (Exception e) {
24             e.printStackTrace();
25         }
26         System.out.println("主线程执行完成");
27     }
28 }

测试结果如下:

>>>
Callable子线程开始计算啦!
主线程在执行其他任务
Callable子线程计算结束!
future.get()-->12497500
主线程执行完成

4.2  使用Callable+FutureTask获取执行结果

Task实现类同上面,测试类如下:

 1 class CallableTest{
 2     void test02(){
 3         //创建线程池
 4         ExecutorService es = Executors.newSingleThreadExecutor();
 5         //创建Callable对象任务
 6         CallableDemo calCallableDemo =new CallableDemo();
 7         //创建FutureTask
 8         FutureTask futureTask=new FutureTask<>(calCallableDemo);
 9         //执行任务
10         es.submit(futureTask);
11         //关闭线程池
12         es.shutdown();
13         try {
14             Thread.sleep(2000);
15             System.out.println("主线程在执行其他任务");
16 
17             if(futureTask.get()!=null){
18                 //输出获取到的结果
19                 System.out.println("futureTask.get()-->"+futureTask.get());
20             }else{
21                 //输出获取到的结果
22                 System.out.println("futureTask.get()未获取到结果");
23             }
24 
25         } catch (Exception e) {
26             e.printStackTrace();
27         }
28         System.out.println("主线程执行完成");
29     }
30 }

测试结果如下:

>>>
Callable子线程开始计算啦!
主线程在执行其他任务
Callable子线程计算结束!
futureTask.get()-->12497500
主线程执行完成