java Callable、Future、FutureTask接口

前言

一般而言,线程的创建方法有两种,一种是继承Thread类,另一种是实现Runnable接口。

但是这两种方法都有一个问题:那就是在任务执行完成之后无法获取返回结果。于是就有了Callable接口,Future接口与FutureTask类的配和取得返回的结果。这也是所谓的“异步”模型。

 

Callable 接口介绍

首先回顾一下java.lang.Runnable接口,就声明了run(),其返回值为void,当然就无法获取结果。

 1 @FunctionalInterface
 2 public interface Runnable {
 3     /**
 4      * When an object implementing interface <code>Runnable</code> is used
 5      * to create a thread, starting the thread causes the object's
 6      * <code>run</code> method to be called in that separately executing
 7      * thread.
 8      * <p>
 9      * The general contract of the method <code>run</code> is that it may
10      * take any action whatsoever.
11      *
12      * @see     java.lang.Thread#run()
13      */
14     public abstract void run();
15 }

而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()的方法,同时这个方法可以有返回值V,也可以抛出异常。

 

那么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 
3 public static <T> Callable<T> callable(Runnable task, T result)  

 

测试代码

 1 public class Task implements Callable {
 2 
 3     @Override
 4     public String call() throws Exception {
 5         return "call()方法";
 6     }
 7 
 8     public static void main(String[] args) throws ExecutionException, InterruptedException {
 9         ExecutorService executorService = Executors.newFixedThreadPool(2);
10         Task task = new Task();
11         Future<String> submit = executorService.submit(task);
12         System.out.println(submit.get());
13     }
14 }

输出结果:call()方法

 

Future<V> 接口介绍

Future<V>接口是用来获取异步计算结果的,说白了就是对具体的Runnable或者Callable对象任务执行的结果进行获取(get()),取消(cancel()),判断是否完成等操作。我们看看Future接口的源码:

1     public interface Future<V> {  
2         boolean cancel(boolean mayInterruptIfRunning);  
3         boolean isCancelled();  
4         boolean isDone();  
5         V get() throws InterruptedException, ExecutionException;  
6         V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException;  
7     }  

方法说明:

V get() :获取异步执行的结果,如果没有结果可用,此方法会阻塞直到异步计算完成。

V get(Long timeout , TimeUnit unit) :获取异步执行结果,如果没有结果可用,此方法会阻塞,但是会有时间限制,如果阻塞时间超过设定的timeout时间,该方法将抛出异常

boolean isDone() :如果任务执行结束,无论是正常结束或是中途取消还是发生异常,都返回true。

boolean isCancelled() :如果任务完成前被取消,则返回true。

boolean cancel(boolean mayInterruptRunning)

  • 如果任务还没开始,执行cancel(...)方法将返回false;
  • 如果任务已经启动,执行cancel(true)方法将以中断执行此任务线程的方式来试图停止任务,如果停止成功,返回true;
  • 当任务已经启动,执行cancel(false)方法将不会对正在执行的任务线程产生影响(让线程正常执行到完成),此时返回false;
  • 当任务已经完成,执行cancel(...)方法将返回false。

cancel()注意是试图取消,并不一定能取消成功。因为任务可能已完成、已取消、或者一些其它因素不能取消,存在取消失败的可能。boolean类型的返回值是“是否取消成功”的意思。参数mayInterruptRunning表示是否采用中断的方式取消线程执行。

通过方法分析我们也知道实际上Future提供了3种功能:(1)能够中断执行中的任务(2)判断任务是否执行完成(3)获取任务执行完成后的结果。

 

测试代码

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

Callable实现类如下:

 1 import java.util.concurrent.Callable;  
 2 
 3 public class CallableDemo implements Callable<Integer> {  
 4       
 5     private int sum;  
 6     @Override  
 7     public Integer call() throws Exception {  
 8         System.out.println("Callable子线程开始计算啦!");  
 9         Thread.sleep(2000);  
10           
11         for(int i=0 ;i<5000;i++){  
12             sum=sum+i;  
13         }  
14         System.out.println("Callable子线程计算结束!");  
15         return sum;  
16     }  
17 } 

 

Callable执行测试类如下:

 1 public class CallableTest {
 2     public static void main(String[] args) {
 3         //创建线程池  
 4         ExecutorService es = Executors.newSingleThreadExecutor();
 5         //创建Callable对象任务  
 6         CallableDemo calTask=new CallableDemo();
 7         //提交任务并获取执行结果  
 8         Future<Integer> future =es.submit(calTask);
 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 }

执行结果:

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

 


但是我们必须明白Future只是一个接口,我们无法直接创建对象,因此就需要其实现类FutureTask登场啦。

 

FutureTask类

FutureTask的几个状态
 1     /**
 2      * The run state of this task, initially NEW.  The run state
 3      * transitions to a terminal state only in methods set,
 4      * setException, and cancel.  During completion, state may take on
 5      * transient values of COMPLETING (while outcome is being set) or
 6      * INTERRUPTING (only while interrupting the runner to satisfy a
 7      * cancel(true)). Transitions from these intermediate to final
 8      * states use cheaper ordered/lazy writes because values are unique
 9      * and cannot be further modified.
10      *
11      * Possible state transitions:
12      * NEW -> COMPLETING -> NORMAL
13      * NEW -> COMPLETING -> EXCEPTIONAL
14      * NEW -> CANCELLED
15      * NEW -> INTERRUPTING -> INTERRUPTED
16      */
17     private volatile int state;
18     private static final int NEW          = 0;
19     private static final int COMPLETING   = 1;
20     private static final int NORMAL       = 2;
21     private static final int EXCEPTIONAL  = 3;
22     private static final int CANCELLED    = 4;
23     private static final int INTERRUPTING = 5;
24     private static final int INTERRUPTED  = 6;

 

先来看看FutureTask的实现

1 public class FutureTask<V> implements RunnableFuture<V> { ...}

FutureTask类实现了RunnableFuture接口,我们看一下RunnableFuture接口的实现:

1 public interface RunnableFuture<V> extends Runnable, Future<V> {  
2     void run();  
3 }  

分析:FutureTask除了实现了Future接口外还实现了Runnable接口(既可以通过Runnable接口实现线程,也可以通过Future取得线程执行完后的结果),因此FutureTask也可以直接提交给Executor执行。

最后我们给出FutureTask的两种构造函数:

1 public FutureTask(Callable<V> callable) {  
2 }  
3 
4 public FutureTask(Runnable runnable, V result) {  
5 }  

 

那FutureTask类有什么用?为什么要有一个FutureTask类?

前面说到了Future只是一个接口,而它里面的cancel,get,isDone等方法要自己实现起来都是非常复杂的。所以JDK提供了一个FutureTask类来供我们使用。

 

测试代码

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

Callable实现类不变,测试类如下

 1 public class CallableTest {
 2     public static void main(String[] args) {
 3         //创建线程池  
 4         ExecutorService es = Executors.newSingleThreadExecutor();
 5         //创建Callable对象任务  
 6         CallableDemo calTask=new CallableDemo();
 7         //创建FutureTask  
 8         FutureTask<Integer> futureTask=new FutureTask<>(calTask);
 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 } 

 

执行结果:

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

 

posted @ 2022-03-29 10:12  r1-12king  阅读(42)  评论(0编辑  收藏  举报