一些业务场景我们需要使用多线程异步执行任务,加快任务执行速度。 Java 提供 Runnable Future 两个接口用来实现异步任务逻辑。
虽然 Future 可以获取任务执行结果,但是获取方式十方不变。我们不得不使用Future#get 阻塞调用线程,或者使用轮询方式判断 Future#isDone 任务是否结束,再获取结果。
创建CompletableFuture对象主要靠下面4个静态方法:
//使用默认线程池(ForkJoinPool 线程池)
static CompletableFuture<Void> runAsync(Runnable runnable) //它的run方法没有返回值
static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier) //而Supplier接口的get方法是有返回值的。
//上面2个方法和下面两个方法的区别就是--可以指定线程池
static CompletableFuture<Void> runAsync(Runnable runnable, Executor executor)
static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier, Executor executor)
创建出CompletableFuture对象后,会自动异步执行runnable.run()方法,或者supplier.get()方法。
对于异步编程需要注意两个问题:
如何解决呢?
可以看出CompletableFuture实现了Future接口,可以通过Future接口来解决。
另外CompletableFuture类还实现了CompletionStage接口,里面有40个方法,哈哈。
CompletionStage接口可以清晰地描述这种任务之间的关系。
//串行关系的方法主要是下面这几个:
CompletionStage<R> thenApply(fn);
CompletionStage<R> thenApplyAsync(fn);
CompletionStage<Void> thenAccept(consumer);
CompletionStage<Void> thenAcceptAsync(consumer);
CompletionStage<Void> thenRun(action);
CompletionStage<Void> thenRunAsync(action);
CompletionStage<R> thenCompose(fn);
CompletionStage<R> thenComposeAsync(fn);
以上方法Async代表的是异步执行fn,consumer或者action,相反就是同步执行了。
下面用实例代码展示:
supplyAsync先启动一个异步流程,之后2,3是串行操作,虽然这是一个异步流程,但是1,2,3是有序串行操作的。
CompletableFuture<String> f0 = CompletableFuture.supplyAsync(
() -> "Hello World") //①
.thenApply(s -> s + " QQ") //②
.thenApply(String::toUpperCase);//③
System.out.println(f0.join());
//输出结果
HELLO WORLD QQ
//下面是描述AND汇聚关系的CompletionStage接口:
CompletionStage<R> thenCombine(other, fn);
CompletionStage<R> thenCombineAsync(other, fn);
CompletionStage<Void> thenAcceptBoth(other, consumer);
CompletionStage<Void> thenAcceptBothAsync(other, consumer);
CompletionStage<Void> runAfterBoth(other, action);
CompletionStage<Void> runAfterBothAsync(other, action);
上面这些接口的区别也是fn,consumer,action三个参数不同。
下面用实例代码展示(烧开水操作和拿茶叶操作必须都实现了才能执行泡茶):
CompletableFuture<String> task1 = CompletableFuture.supplyAsync(()->{
sleep(3,TimeUnit.SECONDS);
return "烧开水";
});
CompletableFuture<String> task2 = CompletableFuture.supplyAsync(()->{
sleep(3,TimeUnit.SECONDS);
return "拿茶叶";
});
//任务3必须在任务1和任务2都执行结束才能执行。
CompletableFuture<String> task3 = task2.thenCombine(task1, (shaokaishui,nacha)->{
sleep(3,TimeUnit.SECONDS);
return "泡茶";
});
System.out.println(task3.join());
OR汇聚关系不知道怎么画,就是当两个任务中只要有一个任务执行完成,就可以接着执行下一任务。
//下面是表示这个关系的CompletionStage的接口方法
CompletionStage applyToEither(other, fn);
CompletionStage applyToEitherAsync(other, fn);
CompletionStage acceptEither(other, consumer);
CompletionStage acceptEitherAsync(other, consumer);
CompletionStage runAfterEither(other, action);
CompletionStage runAfterEitherAsync(other, action);
区别也是fn,consumer,action三个参数的不同。
下面是实例代码:
CompletableFuture<String> f1 = CompletableFuture.supplyAsync(()->{
int t = getRandom(5, 10);
sleep(t, TimeUnit.SECONDS);
return String.valueOf(t);
});
CompletableFuture<String> f2 = CompletableFuture.supplyAsync(()->{
int t = getRandom(5, 10);
sleep(t, TimeUnit.SECONDS);
return String.valueOf(t);
});
CompletableFuture<String> f3 = f1.applyToEither(f2,s -> s);
System.out.println(f3.join());
虽然上面我们提到的fn,consumer,action它们的核心方法都不允许抛出可检查异常,但是却无法限制抛出运行时异常。
非异步编程里面,我们可以使用 try{}catch{}来捕获并处理异常,那在异步编程里面,异常该如何处理呢?例如下面这个异常。
CompletableFuture<Integer> f0 = CompletableFuture.supplyAsync(()->(7/0))
.thenApply(r->r*10);
System.out.println(f0.join());
CompletionStage 接口给我们提供的方案非常简单,比 try{}catch{}还要简单,下面是相关的方法,使用这些方法进行异常处理和串行操作是一样的,都支持链式编程方式。
CompletionStage exceptionally(fn);
CompletionStage<R> whenComplete(consumer);
CompletionStage<R> whenCompleteAsync(consumer);
CompletionStage<R> handle(fn);
CompletionStage<R> handleAsync(fn);
exceptionally的使用非常类似于try{}catch{}中的catch{}。
既然有try{}catch{},那么一定有try{}finally{},whenComplete和handle系列类似于finally{},无论是否有异常,whenComplete都会执行consumer的回调函数consumer,而handle执行回调函数fn。两者的区别是whenComplete() 不支持返回结果,而 handle() 是支持返回结果的。
代码实例(展示如何使用execptionally处理异常):
CompletableFuture<Integer> f0 = CompletableFuture
.supplyAsync(()->7/0))
.thenApply(r->r*10)
.exceptionally(e->0);
System.out.println(f0.join());