一、概要
Future
表示异步计算的结果。它提供了检查计算是否完成的方法,以等待计算的完成,并获取计算的结果。计算完成后只能使用 get
方法来获取结果,如有必要,计算完成前可以阻塞此方法。取消则由 cancel
方法来执行。还提供了其他方法,以确定任务是正常完成还是被取消了。一旦计算完成,就不能再取消计算。如果为了可取消性而使用 Future
但又不提供可用的结果,则可以声明 Future<?>
形式类型、并返回 null 作为底层任务的结果。
二、方法摘要
// 取消任务
boolean cancel(boolean mayInterruptIfRunning);
// 获取任务执行结果
V get() throws InterruptedException, ExecutionException;
// 获取任务执行结果,带有超时时间限制
V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException;
// 判断任务是否已经取消
boolean isCancelled();
// 判断任务是否已经结束
boolean isDone();
三、使用实践
3.1TreadPoolExcutor的submit()方法
这3个submit()
方法之间的区别在于方法参数不同。
- 提交 Runnable 任务
submit(Runnabletask)
:这个方法的参数是一个Runnable接口,Runnable接口的run()
方法是没有返回值的,所以submit(Runnabletask)
这个方法返回的Future仅可以用来断言任务已经结束了,类似于 Thread.join()。 - 提交 Callable 任务
submit(Callable task)
:这个方法的参数是一个Callable接口,它只有一个call()
方法,并且这个方法是有返回值的,所以这个方法返回的 Future 对象可以通过调用其get()
方法来获取任务的执行结果。 - 提交 Runnable 任务及结果引用
submit(Runnable task, Tresult)
:假设这个方法返回的 Future对象是f,f.get()
的返回值就是传给submit()
方法的参数result
。
@Slf4j
public class FutureAndCallableDemo {
public static void main(String[] args) throws InterruptedException, ExecutionException {
ExecutorService executorService = Executors.newSingleThreadExecutor();
// 使用 Callable ,可以获取返回值
Callable<String> callable = () -> {
log.info("进入 Callable 的 call 方法");
// 模拟子线程任务,在此睡眠 5s,
// 小细节:由于 call 方法会抛出 Exception,这里不用像使用 Runnable 的run 方法那样 try/catch 了
Thread.sleep(5000);
return "Hello from Callable";
};
log.info("提交 Callable 到线程池");
Future<String> future = executorService.submit(callable);
log.info("主线程继续执行");
log.info("主线程等待获取 Future 结果");
// Future.get() blocks until the result is available
String result = future.get();
log.info("主线程获取到 Future 结果: {}", result);
executorService.shutdown();
}
}
submit(Runnable task, T result)
的使用实践过了,同时还发现上述三个submit
方法返回值都是 Future 接口,Future接口有5个方法,分别是取消任务的方法cancel()
、判断任务是否已取消的方法isCancelled()
、判断任务是否已结束的方法 isDone()
以及2 个获得任务执行结果的 get()
和 get(timeout, unit)
,其中最后一个 get(timeout, unit)
支持超时机制。通过 Future 接口提交的任务不但能够获取任务执行结果,还可以取消任务。不过需要注意的是:这两个get()
方法都是阻塞式的,如果被调用的时候,任务还没有执行完,那么调用 get()
方法的线程会阻塞,直到任务执行完才会被唤醒。
while(!future.isDone()) {
System.out.println("子线程任务还没有结束...");
Thread.sleep(1000);
double elapsedTimeInSec = (System.nanoTime() - startTime)/1000000000.0;
// 如果程序运行时间大于 1s,则取消子线程的运行
if(elapsedTimeInSec > 1) {
future.cancel(true);
}
}
四、FutureTask
Future 是一个接口,而FutureTask是一个工具类,它们的参数和前面介绍的submit()
方法类似
FutureTask 实现了 Runnable 和 Future 接口,由于实现了 Runnable 接口,所以可以FutureTask 对象作为任务提交给ThreadPoolExecutor 去执行,也可以直接被Thread 执行;又因为实现了 Future 接口,所以也能用来获得任务的执行结果。可以将FutureTask 对象提交给ThreadPoolExecutor 执行。
@Slf4j
public class FutureTaskDemo {
public static void main(String[] args) throws InterruptedException, ExecutionException {
// 使用FutureTask
FutureTask<Integer>futureTask=new FutureTask<>(()->10+22);
//创建线程池
ExecutorService executorService = Executors.newSingleThreadExecutor();
log.info("提交 FutureTask 到线程池");
Future<String> future = executorService.submit(futureTask);
log.info("主线程继续执行");
log.info("主线程等待获取 Future 结果");
String result = future.get();
log.info("主线程获取到 Future 结果: {}", result);
executorService.shutdown();
}
}