正文
通过任务执行服务调用外部服务,一般返回Future,表示异步结果,示例代码为:
public static Future
callExternalService(){
return executor.submit(externalTask);
}
在主程序中,结合异步任务和本地调用的示例代码为:
public static void master() {
// 执行异步任务
Future
asyncRet = callExternalService();
// 执行其他任务 ...
// 获取异步任务的结果,处理可能的异常
try {
Integer ret = asyncRet.get();
System.out.println(ret);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
基本的CompletableFuture
使用CompletableFuture可以实现类似功能,不过,它不支持使用Callable表示异步任务,而支持Runnable和Supplier,Supplier替代Callable表示有返回结果的异步任务,与Callale的区别是,它不能抛出受检异常,如果会发生异常,可以抛出运行时异常。
使用Supplier表示异步任务,代码与Callable类似,替换变量类型即可,即:
static Supplier
externalTask = () -> {
int time = delayRandom(20, 2000);
return time;
};
使用CompletableFuture调用外部服务的代码可以为:
public static Future
callExternalService(){
return CompletableFuture.
supplyAsync
(externalTask, executor);
}
supplyAsync是一个静态方法,其定义为:
public static
CompletableFuture
supplyAsync
(
Supplier
supplier, Executor executor)
它接受两个参数supplier和executor,内部,它使用executor执行supplier表示的任务,返回一个CompletableFuture,调用后,任务被异步执行,这个方法立即返回。
supplyAsync还有一个不带executor参数的方法:
public static
CompletableFuture
supplyAsync(Supplier
supplier)
没有executor,任务被谁执行呢?与系统环境和配置有关,一般来说,如果可用的CPU核数大于2,会使用Java 7引入的Fork/Join任务执行服务,即ForkJoinPool.commonPool(),该任务执行服务背后的工作线程数一般为CPU核数减1,即Runtime.getRuntime().availableProcessors()-1,否则,会使用ThreadPerTaskExecutor,它会为每个任务创建一个线程。
对于CPU密集型的运算任务,使用Fork/Join任务执行服务是合适的,但对于一般的调用外部服务的异步任务,Fork/Join可能是不合适的,因为它的并行度比较低,可能会让本可以并发的多任务串行运行,这时,应该提供Executor参数。
后面我们还会看到很多以Async结尾命名的方法,一般都有两个版本,一个带Executor参数,另一个不带,其含义是相同的,就不再重复介绍了。
对于类型为Runnable的任务,构建CompletableFuture的方法为:
public static CompletableFuture
runAsync
(
Runnable runnable)
public static CompletableFuture
runAsync
(
Runnable runnable, Executor executor)
它与supplyAsync是类似的,具体就不赘述了。
CompletableFuture对Future的基本增强
Future有的接口,CompletableFuture都是支持的,不过,CompletableFuture还有一些额外的相关方法,比如:
public T join()
public boolean isCompletedExceptionally()
public T getNow(T valueIfAbsent)
join与get方法类似,也会等待任务结束,但它不会抛出受检异常,如果任务异常结束了,join会将异常包装为运行时异常CompletionException抛出。
Future有isDone方法检查任务是否结束了,但不知道任务是正常结束还是异常结束,isCompletedExceptionally方法可以判断任务是否是异常结束了。
getNow与join类似,区别是,如果任务还没有结束,它不会等待,而是会返回传入的参数valueIfAbsent。
进一步理解Future/CompletableFuture
前面例子都使用了任务执行服务,其实,任务执行服务与异步结果Future不是绑在一起的,可以自己创建线程返回异步结果,为进一步理解,我们看些示例。
使用FutureTask调用外部服务,代码可以为:
public static Future
callExternalService() {
FutureTask
future = new FutureTask<>(externalTask);
new Thread() {
public void run() {
future.run();
}
}.start();
return future;
}
内部自己创建了一个线程,线程调用FutureTask的run方法,我们在
77节
分析过FutureTask的代码,run方法会调用externalTask的call方法,并保存结果或碰到的异常,唤醒等待结果的线程。
使用CompletableFuture,也可以直接创建线程,并返回异步结果,代码可以为: