Runnable、Callable、Future和CompletableFuture

2023-04-25,,

一、Runnable

Runnable非常简单,只需要实现一个run方法即可,没有参数,也没有返回值。可以以new Thread的方式去运行,当然更好的方式在放到excutorPool中去运行。

二、Callabe和Future

Callable也用来实现异步调用,但是可以返回参数,并可以抛出异常。

Future可以认为是对java异步执行机制的另一种包装。

 public interface Future<V> {

     boolean cancel(boolean mayInterruptIfRunning);

     boolean isCancelled();

     boolean isDone();

     V get() throws InterruptedException, ExecutionException;

     V get(long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException;
}

通过Future,可以知道异步线程是否完成了任务,并可以拿到相应的值。

FutureTask是Future的实现类,在具体的使用中可以和Callable一起使用。

 public Class FutureTask<V> implements Runnable, Future<V>, RunnableFuture<V>{

 public FutureTask(Callable<V> callable);

 public FutureTask(Runnable runnable, V result);

 public boolean cancel(boolean mayInterruptIfRunning);

 public V get();

 public V get(long timeout,TimeUnit unit);

 public boolean isCancelled();

 public boolean isDone();

 public void run();

 }

举例如下。

 ……

 Callable<String> callable = new Callable<String>(){
@override
public String call() throws Exception{
Thread.sleep(5000);
return "Done";
}
}
FutureTask<String> task = new FutureTask<String>(callable); new Thread(task).start(); ....... if(task.isDone() == false){//异步操作未结束
//do something here,
} String str = task.get(); ........

首先构建了一个callable方法,紧接着构建了FutureTask并启动了它。

第16行开始等待这个异步任务结束,其实也可以没有16行,直接进行20行,但20行也是个阻塞方法,会一直等待任务结束。

三、ListenableFuture和CompletableFuture

在上面Future的实现中,可以看到需要主线程一直判断任务是否完成,google对Future进行了扩展,就是ListenableFuture,增加了void addListener(Runnable listener, Executor executor)方法,这样,一旦异步任务执行完毕,就可以迅速拿到结果,更加自然。

代码片段一: 获取ListenableFuture

 ListeningExecutorService executorService = MoreExecutors.listeningDecorator(Executors.newCachedThreadPool());
final ListenableFuture<Integer> listenableFuture = executorService.submit(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
System.out.println("call execute..");
TimeUnit.SECONDS.sleep(1);
return 7;
}
});

代码片段二:通过ListenableFuture的addListener方法来实现回调

 listenableFuture.addListener(new Runnable() {
@Override
public void run() {
try {
System.out.println("get listenable future's result " + listenableFuture.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}, executorService);

代码片段三:通过Futures的静态方法addCallback给ListenableFuture添加回调函数

  Futures.addCallback(listenableFuture, new FutureCallback<Integer>() {
@Override
public void onSuccess(Integer result) {
System.out.println("get listenable future's result with callback " + result);
} @Override
public void onFailure(Throwable t) {
t.printStackTrace();
}
})

推荐使用第二种方法,因为第二种方法可以直接得到Future的返回值,或者处理错误情况。本质上第二种方法是通过调动第一种方法实现的,做了进一步的封装。

那么,是否就可以用ListenableFuture完全取代Future了?其实也要看情况

1、如果一个主任务开始执行,然后需要执行各个小任务,并且需要等待返回结果,统一返回给前端,此时Future和ListenableFuture作用几乎差不多,都是通过get()方法阻塞等待每个任务执行完毕返回。

2、 如果一个主任务开始执行,然后执行各个小任务,主任务不需要等待每个小任务执行完,不需要每个小任务的结果,此时用ListenableFuture非常合适,它提供的FutureCallBack接口可以对每个任务的成功或失败单独做出响应。

CompletableFuture是JDK8中才出现的概念,算是对google的ListenableFuture的回应吧,而且更强大。

CompletableFuture能够将回调放到与任务不同的线程中执行,也能将回调作为继续执行的同步函数,在与任务相同的线程中执行。它避免了传统回调最大的问题,那就是能够将控制流分离到不同的事件处理器中。

CompletableFuture弥补了Future模式的缺点。在异步的任务完成后,需要用其结果继续操作时,无需等待。可以直接通过thenAccept、thenApply、thenCompose等方式将前面异步处理的结果交给另外一个异步事件处理线程来处理。

获得一个CompletableFuture

runAsync(Runnable runnable) 使用ForkJoinPool.commonPool()作为它的线程池执行异步代码。
runAsync(Runnable runnable, Executor executor) 使用指定的thread pool执行异步代码。
supplyAsync(Supplier<U> supplier) 使用ForkJoinPool.commonPool()作为它的线程池执行异步代码,异步操作有返回值
supplyAsync(Supplier<U> supplier, Executor executor) 使用指定的thread pool执行异步代码,异步操作有返回值

可以使用上面的静态类来获取一个CompletableFuture,前两个和后两个的区别,也类同与Runnable和Callable的区别。

 CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
System.out.println("Hello");
}); try {
future.get();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
} System.out.println("CompletableFuture");
  CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> "Hello");

         try {
System.out.println(future.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
} System.out.println("CompletableFuture");

如果只是看上面的代码,好像和Future没什么区别,其实CompletableFuture提供的更多更强大的功能。

计算结果完成时的处理

 public CompletableFuture<T>     whenComplete(BiConsumer<? super T,? super Throwable> action)
public CompletableFuture<T> whenCompleteAsync(BiConsumer<? super T,? super Throwable> action)
public CompletableFuture<T> whenCompleteAsync(BiConsumer<? super T,? super Throwable> action, Executor executor)
public CompletableFuture<T> exceptionally(Function<Throwable,? extends T> fn)

转换

 public <U> CompletableFuture<U>     thenApply(Function<? super T,? extends U> fn)
public <U> CompletableFuture<U> thenApplyAsync(Function<? super T,? extends U> fn)
public <U> CompletableFuture<U> thenApplyAsync(Function<? super T,? extends U> fn, Executor executor)

还有其他更多的语义,无法一一赘述。

参考了如下文档

https://www.jianshu.com/p/4897ccdcb278

Runnable、Callable、Future和CompletableFuture的相关教程结束。

《Runnable、Callable、Future和CompletableFuture.doc》

下载本文的Word格式文档,以方便收藏与打印。