Executor框架(七)Future 接口、FutureTask类
Future接口介绍
Future 表示异步计算的结果。它提供了检查计算是否完成的方法,以等待计算的完成,并获取计算的结果。
Future 一般由 ExecutorService 的submit()、invokeAll()方法返回的,用于跟踪、获取任务在线程池中的运行情况、等待运算结果,还可以取消任务。(还有其子接口 ScheduleFuture 则由 ScheduleExecutorService 的schedule()等方法返回);
方法描述
boolean cancel(boolean mayInterruptIfRunning):
试图取消对此任务的执行。分成以下三种情况:
- 如果任务尚未启动,则此任务将永不运行。
- 如果任务已经启动,则 mayInterruptIfRunning 参数确定是否 中断这个任务来尝试停止任务。同时,任务也应该要对中断敏感。
- 任务已完成、或已取消,或者由于某些其他原因而无法取消,返回false。
注意: 此方法返回后,对 isDone() 的后续调用将始终返回 true。但如果此方法返回 true,则对 isCancelled() 的后续调用才将始终返回
boolean isCancelled(): 如果在任务正常完成前将其取消,则返回 true。
boolean isDone(): 如果任务已完成,则返回 true。 可能由于正常终止、异常或取消而完成,在所有这些情况中,此方法都将返回 true。
获取计算结果
获取计算结果的方法,JDK提供了两个方法:阻塞获取 与 超时等待获取。这两个方法会抛出 CancellationException(任务被取消时)、InterruptedException
V get(): 等待计算完成,然后获取其结果。
V get(long timeout,TimeUnit unit): 最多等待为使计算完成所给定的时间之后,获取其结果(如果结果可用)。
@ Example 示例
下面的例子,是在单线程的线程池中提交两个任务(任务A、任务B)。
public class Test {
public static void main(String[] args) throws InterruptedException, ExecutionException {
//单线程的线程池
ExecutorService executor = Executors.newSingleThreadExecutor();
//提交两个任务
Future futureA = executor.submit(new MyCallable("futureA"));
Future futureB = executor.submit(new MyCallable("futureB"));
Thread.sleep(1000);
//在运行一秒后,判断任务A是否完成
if(futureA.isDone()){
//如果完成,则直接获取结果
double result = (double) futureA.get();
System.out.println("运算结果是:"+result);
}else{
//如果没有完成,则取消任务A
boolean b = futureA.cancel(false);
System.out.println("futureA 执行了cancel方法,返回的值是:"+b);
}
//取消任务B
futureB.cancel(false);
}
}
class MyCallable implements Callable{
String taskName;
public MyCallable(String taskName){
this.taskName = taskName;
}
@Override
public Object call() {
try {
//模拟任务的执行时间为 2s
for(int i=0;i<5;i++){
Thread.sleep(400);
System.out.println(taskName+"任务正在运行中.....");
}
} catch (InterruptedException e) {
e.printStackTrace();
}
double d = Math.random() * 10;
return d;
}
}
运行结果:
futureA任务正在运行中.....
futureA任务正在运行中.....
futureA 执行了cancel方法,返回的值是:true
futureA任务正在运行中.....
futureA任务正在运行中.....
futureA任务正在运行中.....
任务A是在执行时被取消的,调用的cancel(false)
方法返回的结果为true,但是任务并没有真的停止执行。任务B则是在还没被执行时取消的,所以任务B在后续的时间内,没有执行。
可以得出结论,cancel( false)
方法是取消尚未被执行的任务、周期任务,而不是停止正在执行的任务。当然,如果想要停止正在执行的任务,任务里面必须是中断敏感,然后 cancel(true)
,参数为true,即在cancel的同时,也发出中断信号。
//简单的中断处理,发现中断退出
public void run(){
while(!Thread.interrupted()){
//.....
}
}
FutureTask 介绍
FutureTask 是一个可取消的异步计算任务,是一个独立的类,实现了 Future、Runnable接口。FutureTask 的出现是为了弥补 Thread 的不足而设计的,可以让程序员跟踪、获取任务的执行情况、计算结果 。
因为 FutureTask 实现了 Runnable,所以 FutureTaskk 可以作为参数来创建一个新的线程来执行,也可以提交给 Executor 执行。FutureTask 一旦计算完成,就不能再重新开始或取消计算。
构造方法
FutureTask(Callable
创建一个 FutureTask,一旦运行就执行给定的 Callable。
FutureTask(Runnable runnable, V result)
创建一个 FutureTask,一旦运行就执行给定的 Runnable,并安排成功完成时 get 返回给定的结果 。
应用场景
FutureTask 可用于异步获取执行结果或可以取消执行任务的场景;
@ Example 简单例子
下面的例子中,因为计算数据的时间比较长,所以main线程就额外起一个异步线程来计算数据,从而使得计算数据的同时,main线程可以做其他工作,直到需要用到计算结果时,才去获取计算结果。
需要注意的是,线程 thread2 并没有执行 FutureTask,因为 FutureTask 已经在线程 thread 中完成了。一旦 FutureTask 计算完成,就不能再重新开始或取消计算。
public class Test {
public static void main(String[] args) throws InterruptedException, ExecutionException {
FutureTask<Double> task = new FutureTask(new MyCallable());
//创建一个线程,异步计算结果
Thread thread = new Thread(task);
thread.start();
//主线程继续工作
Thread.sleep(1000);
System.out.println("主线程等待计算结果...");
//当需要用到异步计算的结果时,阻塞获取这个结果
Double d = task.get();
System.out.println("计算结果是:"+d);
//用同一个 FutureTask 再起一个线程
Thread thread2 = new Thread(task);
thread2.start();
}
}
class MyCallable implements Callable<Double>{
@Override
public Double call() {
double d = 0;
try {
System.out.println("异步计算开始.......");
d = Math.random()*10;
d += 1000;
Thread.sleep(2000);
System.out.println("异步计算结束.......");
} catch (InterruptedException e) {
e.printStackTrace();
}
return d;
}
}
运行结果:
异步计算开始.......
主线程等待计算结果...
异步计算结束.......
计算结果是:1002.7806590582911
除了实现Future、Runnable外,此类还提供了几个protected方法,用于扩展此类
protected void done()
当此任务转换到状态 isDone(不管是正常地还是通过取消)时,调用受保护的方法。默认实现不执行任何操作。
protected void set(V v)
除非已经设置了此 Future 或已将其取消,否则将其结果设置为给定的值。在计算成功完成时通过 run 方法内部调用此方法。
protected void setException(Throwable t)
除非已经设置了此 Future 或已将其取消,否则它将报告一个 ExecutionException,并将给定的 throwable 作为其原因。在计算失败时通过 run 方法内部调用此方法。
protected boolean runAndReset()
执行计算而不设置其结果,然后将此 Future 重置为初始状态,如果计算遇到异常或已取消,则该操作失败。本操作被设计用于那些本质上要执行多次的任务。