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 主线程在执行完成