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

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

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

 

1、Callable<V>接口

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

而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 }
可以看到Callable是一个函数式接口。同时Callable是有返回值的,并且支持泛型。
 
论是Runnable接口的实现类还是Callable接口的实现类,都可以被ThreadPoolExecutor或ScheduledThreadPoolExecutor执行, ThreadPoolExecutor或ScheduledThreadPoolExecutor都实现了 ExcutorService接口, 而因此 Callable需要和Executor框架中的ExcutorService结合使用,我们先看看ExecutorService提供的方法:
  
1 <T> Future<T> submit(Callable<T> task);
2 <T> Future<T> 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<Object> callable(Runnable task)
2 public static <T> Callable<T> callable(Runnable task, T result)

 

2、Future<V>接口

Future<V>接口是用来获取异步计算结果的,说白了就是对具体的Runnable或者Callable对象任务执行的结果进行获取(get()),取消(cancel()),判断是否完成等操作。我们看看Future接口的源码:
 1 public interface Future<V> {
 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      * <p>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<V> implements RunnableFuture<V> { }
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 <V> The result type returned by this Future's {@code get} method
10  */
11 public interface RunnableFuture<V> extends Runnable, Future<V> {
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<V> callable) {
2 }
3 public FutureTask(Runnable runnable, V result) {
4 }

 

 

4、Callable<V>/Future<V>/FutureTask的使用

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

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

Task实现类如下:

 1 public class CallableDemo implements Callable<Integer> {
 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<Integer> 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<Integer> 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
主线程执行完成

 

 

posted @ 2021-12-13 22:03  r1-12king  阅读(156)  评论(0编辑  收藏  举报