Java8中的CompletableFuture是对Future的扩展实现, 主要是为了弥补Future没有相应的回调机制的缺陷。
public class CompletableFuture<T> implements Future<T>, CompletionStage<T>
CompletableFuture实现了Future和CompletionStage两个接口。
先看看 Java8 之前的 Future 的使用:
package demos;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class Demo {
public static void main(String[] args) throws ExecutionException, InterruptedException {
ExecutorService cachePool = Executors.newCachedThreadPool();
Future<String> future = cachePool.submit(() -> {
Thread.sleep(3000);
return "异步任务计算结果!";
});
// 提交完异步任务后, 主线程可以继续干一些其他的事情.
doSomeThingElse();
// 为了获取异步计算结果, 我们可以通过 future.get 和 轮询机制来获取.
String result;
// Get 方式会导致当前线程阻塞, 这显然违背了异步计算的初衷.
// result = future.get();
// 轮询方式虽然不会导致当前线程阻塞, 但是会导致高额的 CPU 负载.
long start = System.currentTimeMillis();
while (true) {
if (future.isDone()) {
break;
}
}
System.out.println("轮询耗时:" + (System.currentTimeMillis() - start));
result = future.get();
System.out.println("获取到异步计算结果啦: " + result);
cachePool.shutdown();
}
private static void doSomeThingElse() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("重要的事情干完了,获取异步计算结果来执行剩下的事情.");
}
}
输出结果:
重要的事情干完了, 获取异步计算结果来执行剩下的事情.
轮询耗时:2000
获取到异步计算结果啦: 异步任务计算结果!
从上面的 Demo 中我们可以看出, future 在执行异步任务时, 对于结果的获取显的不那么优雅, 很多第三方库就针对 Future 提供了回调式的接口以用来获取异步计算结果, 如Google的: ListenableFuture, 而 Java8 所提供的 CompletableFuture 便是官方为了弥补这方面的不足而提供的 API。
简单介绍CompletableFuture的用法:
package demos;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class CompleteFutureDemo {
public static void main(String[] args) throws ExecutionException, InterruptedException {
CompletableFuture<String> completableFutureOne = new CompletableFuture<>();
ExecutorService cachePool = Executors.newCachedThreadPool();
cachePool.execute(() -> {
try {
Thread.sleep(3000);
completableFutureOne.complete("异步任务执行结果");
System.out.println(Thread.currentThread().getName());
} catch (InterruptedException e) {
e.printStackTrace();
}
});
// WhenComplete 方法返回的 CompletableFuture 仍然是原来的 CompletableFuture 计算结果
CompletableFuture<String> completableFutureTwo = completableFutureOne.whenComplete((s, throwable) -> {
System.out.println("当异步任务执行完毕时打印异步任务的执行结果: " + s);
});
// ThenApply 方法返回的是一个新的 completeFuture.
CompletableFuture<Integer> completableFutureThree = completableFutureTwo.thenApply(s -> {
System.out.println("当异步任务执行结束时, 根据上一次的异步任务结果, 继续开始一个新的异步任务!");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return s.length();
});
System.out.println("阻塞方式获取执行结果:" + completableFutureThree.get());
cachePool.shutdown();
}
}
1、runAsync/supplyAsync-创建CompletableFuture对象
一个CompletableFuture对象代表着一个任务。
创建CompletableFuture对象:
public static <U> CompletableFuture<U> completedFuture(U value)
CompletableFuture.completedFuture是一个静态辅助方法,用来返回一个已经计算好的CompletableFuture。
而以下四个静态方法用来为一段异步执行的代码创建CompletableFuture对象:
public static CompletableFuture<Void> runAsync(Runnable runnable)
public static CompletableFuture<Void> runAsync(Runnable runnable, Executor executor)
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier)
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier, Executor executor)
以Async结尾并且没有指定Executor的方法会使用ForkJoinPool.commonPool()作为它的线程池执行异步代码。runAsync方法也好理解,它以Runnable函数式接口类型为参数,所以CompletableFuture的计算结果为空。supplyAsync方法以Supplier<U>函数式接口类型为参数,CompletableFuture的计算结果类型为U。
CompletableFuture中的很多静态方法主要可以分成两类,方法后缀带不带Async,不带Async后缀的静态方法表示执行该方法的是当前线程,带Async后缀的静态方法表示使用其他线程异步执行,如果方法参数中指定了线程池,则使用指定线程池去执行任务,如果方法参数中没有指定线程池,则默认使用ForkJoinPool.commonPool()作为它的线程池执行异步代码。
2、whenComplete
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)
whenComplete方法是任务执行完毕时调用的,传入一个action。这个方法的执行线程是当前线程,意味着会阻塞当前线程。
看两个例子:
package com.multi.thread.completableFuture.study;
import java.util.concurrent.CompletableFuture;
import java.util.function.BiConsumer;
import java.util.function.Supplier;
public class WhenCompleteMethodStudy {
public static void main(String[] args) {
CompletableFuture completableFuture = CompletableFuture.supplyAsync(() -> {
System.out.println("Start creating CompletableFuture.");
sleep(100);
System.out.println("The thread of creating CompletableFuture is: " + Thread.currentThread());
return "成功创建CompletableFuture。";
});
System.out.println("===111-main线程执行操作===" + Thread.currentThread());
// sleep(500);
System.out.println("===222-main线程继续操作===" + Thread.currentThread());
completableFuture.whenComplete((o1, o2) -> {
System.out.println("The thread of call whenComplete method is: " + Thread.currentThread());
sleep(200);
System.out.println("输出结果:" + o1);
});
System.out.println("===main线程休息===" + Thread.currentThread());
// sleep(1000);
System.out.println("===main线程结束===" + Thread.currentThread());
}
private static void sleep(int time) {
try {
Thread.sleep(time);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
如上图所示,如果主线程很快执行结束了,可能会使得要执行CompletableFuture.supplyAsync方法中任务的线程还未来得及执行。
如果在执行CompletableFuture.supplyAsync任务与执行completableFuture.whenComplete任务之间没有耗时的操作,如上图所示,主线程都快执行结束了,执行执行CompletableFuture.supplyAsync任务的线程还未结束,等到该任务执行结束,就会使用这个线程继续执行completableFuture.whenComplete任务,不是主线程执行completableFuture.whenComplete任务任务。
根据测试得出的结论是:如果调用whenComplete的中途,还发生了其他事情,如图中主线程的sleep(500),导致completableFuture这个任务执行完毕了,主线程还未执行到whenComplete方法,那么就使用主线程调用。如果主线程在中途没有发生耗时操作,且在触碰到whenComplete方法时completableFuture这个任务还没有彻底执行完毕,那么就会用completableFuture这个任务所使用的线程去执行whenComplete。
whenCompleteAsync方法是新创建一个异步线程执行任务,所以不会阻塞主线程。
package com.study;
import java.util.Random;
import java.util.concurrent.CompletableFuture;
public class WhenCompleteDemo {
private static Random rand = new Random();
private static long t = System.currentTimeMillis();
static int getMoreData() {
System.out.println("begin to start compute");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("end to start compute. passed " + (System.currentTimeMillis() - t)/1000 + " seconds");
return rand.nextInt(1000);
}
public static void main(String[] args) throws Exception {
CompletableFuture<Integer> futureOne = CompletableFuture.supplyAsync(WhenCompleteDemo::getMoreData);
//whenComplete方法会得到一个CompletableFuture对象,
//该对象的结果就是调用whenComplete方法的CompletableFuture对象的结果,
//即futureTwo的结果就是futureOne的结果
CompletableFuture<Integer> futureTwo = futureOne.whenComplete((v, e) -> {
System.out.println("futureOne的结果:" + v);
System.out.println("futureOne的异常:" + e);
});
System.out.println("主线程阻塞等待获取执行结果");
//futureTwo的结果就是futureOne的结果
System.out.println("阻塞获取futureTwo的结果:" + futureTwo.get());
}
}
3、handle/thenApply
下面一组方法虽然也返回CompletableFuture对象,但是新的CompletableFuture对象的值和原来的CompletableFuture计算的值不同。当原先的CompletableFuture的值计算完成或者抛出异常的时候,会触发新的CompletableFuture对象的计算,结果由BiFunction参数计算而得。因此这组方法兼有whenComplete和转换的两个功能。
public <U> CompletableFuture<U> handle(BiFunction<? super T,Throwable,? extends U> fn)
public <U> CompletableFuture<U> handleAsync(BiFunction<? super T,Throwable,? extends U> fn)
public <U> CompletableFuture<U> handleAsync(BiFunction<? super T,Throwable,? extends U> fn, Executor executor)
同样,不以Async结尾的方法由原来的线程计算,以Async结尾的方法由默认的线程池ForkJoinPool.commonPool()或者指定的线程池executor运行。
下面一组方法与上面的一组handle方法类似,都是根据原来的CompletableFuture对象生成新的CompletableFuture对象。
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)
这一组函数的功能是当原来的CompletableFuture计算完后,将结果传递给函数fn,将fn的结果作为新的CompletableFuture计算结果。因此它的功能相当于将CompletableFuture<T>转换成CompletableFuture<U>。
这三个函数的区别和上面介绍的一样,不以Async结尾的方法由原来的线程计算,以Async结尾的方法由默认的线程池ForkJoinPool.commonPool()或者指定的线程池executor运行。
public static void thenApplyTest(){
CompletableFuture<Integer> future=CompletableFuture.supplyAsync(()->{
return 100;
});
CompletableFuture<String> f=future.thenApplyAsync(i->i*10).thenApply(i->i.toString());
try{
System.out.println("thenApply测试结果:"+f.get());
}catch(InterruptedException|ExecutionException e){
e.printStackTrace();
}
}
输出结果:
thenApply测试结果:1000
需要注意的是,这些转换并不是马上执行的,也不会阻塞,而是在前一个stage完成后继续执行。
它们与handle方法的区别在于handle方法会处理正常计算值和异常,因此它可以屏蔽异常,避免异常继续抛出。而thenApply方法只是用来处理正常值,因此一旦有异常就会抛出。
4、thenAccept
上面的方法是当计算完成的时候,会生成新的计算结果(thenApply, handle),或者返回同样的计算结果whenComplete,CompletableFuture还提供了一种处理结果的方法,只对结果执行Action,而不返回新的计算值,因此计算值为Void。
public CompletableFuture<Void> thenAccept(Consumer<? super T> action)
public CompletableFuture<Void> thenAcceptAsync(Consumer<? super T> action)
public CompletableFuture<Void> thenAcceptAsync(Consumer<? super T> action, Executor executor)
看它的参数类型也就明白了,它们是函数式接口Consumer,这个接口只有输入,没有返回值。
public static void thenAcceptTest(){
CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
return 100;
});
CompletableFuture<Void> f = future.thenAccept(i ->
System.out.println("supplyAsync方法生成的CompletableFuture的结果:" + i));
try {
System.out.println("thenAccept方法生成的CompletableFuture的结果:" + f.get());
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
输出结果:
supplyAsync方法生成的CompletableFuture的结果:100
thenAccept方法生成的CompletableFuture的结果:null
5、thenAcceptBoth
public <U> CompletableFuture<Void> thenAcceptBoth(CompletionStage<? extends U> other, BiConsumer<? super T,? super U> action)
public <U> CompletableFuture<Void> thenAcceptBothAsync(CompletionStage<? extends U> other, BiConsumer<? super T,? super U> action)
public <U> CompletableFuture<Void> thenAcceptBothAsync(CompletionStage<? extends U> other, BiConsumer<? super T,? super U> action, Executor executor)
public CompletableFuture<Void> runAfterBoth(CompletionStage<?> other, Runnable action)
thenAcceptBoth方法表示当两个CompletionStage都正常完成计算的时候,就会执行提供的action,它用来组合另外一个异步的结果。此处的两个CompletionStage是指:一个是调用thenAcceptBoth方法的CompletionStage,另一个是thenAcceptBoth方法参数中的CompletionStage。CompletableFuture是CompletionStage接口的实现类。
runAfterBoth是当两个CompletionStage都正常完成计算的时候,执行一个Runnable,这个Runnable并不使用计算的结果。
public static void thenAcceptBothTest(){
CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
return 100;
});
CompletableFuture<Void> f = future.thenAcceptBoth(
CompletableFuture.completedFuture(10), (x, y)
-> System.out.println("将两个CompletableFuture的结果组合:" + x * y));
try {
System.out.println("thenAcceptBoth方法生成的CompletableFuture的结果:" + f.get());
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
输出结果:
将两个CompletableFuture的结果组合:1000
thenAcceptBoth方法生成的CompletableFuture的结果:null
6、thenRun
更彻底地,下面一组方法当计算完成的时候会执行一个Runnable,与thenAccept不同,Runnable并不使用CompletableFuture计算的结果,Runnable接口的run方法既没有参数也没有返回值。因此先前的CompletableFuture计算的结果被忽略了,这个方法返回CompletableFuture<Void>类型的对象。
public CompletableFuture<Void> thenRun(Runnable action)
public CompletableFuture<Void> thenRunAsync(Runnable action)
public CompletableFuture<Void> thenRunAsync(Runnable action, Executor executor)
public static void thenRunTest(){
CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
return 100;
});
CompletableFuture<Void> f = future.thenRun(() ->
System.out.println("原来的CompletableFuture的结果被丢弃了"));
try {
System.out.println("thenRun方法生成的CompletableFuture的结果:" + f.get());
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
输出结果:
原来的CompletableFuture的结果被丢弃了
thenRun方法生成的CompletableFuture的结果:null
因此,可以根据方法参数的类型来加速记忆:Runnable类型的参数会忽略计算的结果,Consumer是纯消费计算结果,BiConsumer会组合另外一个CompletionStage纯消费,Function会对计算结果做转换,BiFunction会组合另外一个CompletionStage的计算结果做转换。
7、thenCompose/thenCombine
public <U> CompletableFuture<U> thenCompose(Function<? super T,? extends CompletionStage<U>> fn)
public <U> CompletableFuture<U> thenComposeAsync(Function<? super T,? extends CompletionStage<U>> fn)
public <U> CompletableFuture<U> thenComposeAsync(Function<? super T,? extends CompletionStage<U>> fn, Executor executor)
这一组方法接受一个Function作为参数,这个Function的输入是当前的CompletableFuture的计算值,返回结果将是一个新的CompletableFuture,这个新的CompletableFuture会组合原来的CompletableFuture的结果。
public static void thenComposeTest() {
CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
return 100;
});
/*CompletableFuture<String> f1 = future.thenCompose(
new Function<Integer, CompletionStage<String>>() {
@Override
public CompletionStage<String> apply(Integer i) {
return CompletableFuture.supplyAsync(new Supplier<String>() {
@Override
public String get() {
return (i * 10) + "";
}
});
}
});*/
//上面这段代码的lambda表达式的写法如下
CompletableFuture<String> f = future.thenCompose(i -> {
return CompletableFuture.supplyAsync(() -> {
return (i * 10) + "";
});
});
try {
System.out.println(f.get());
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
下面的一组方法thenCombine也是用来复合另外一个CompletionStage的结果。两个CompletionStage是并行执行的,它们之间并没有先后依赖顺序,other并不会等待先前的CompletableFuture执行完毕后再执行。其实从功能上来讲,它们的功能更类似thenAcceptBoth,只不过thenAcceptBoth是纯消费,它的函数参数没有返回值,而thenCombine的函数参数fn有返回值。
public <U,V> CompletableFuture<V> thenCombine(CompletionStage<? extends U> other, BiFunction<? super T,? super U,? extends V> fn)
public <U,V> CompletableFuture<V> thenCombineAsync(CompletionStage<? extends U> other, BiFunction<? super T,? super U,? extends V> fn)
public <U,V> CompletableFuture<V> thenCombineAsync(CompletionStage<? extends U> other, BiFunction<? super T,? super U,? extends V> fn, Executor executor)
public static void thenCombineTest(){
CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
return 100;
});
CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> {
return "abc";
});
CompletableFuture<String> f = future.thenCombine(future2, (x,y) -> y + "-" + x);
/*CompletableFuture<String> f1 = future.thenCombine(future2,
new BiFunction<Integer, String, String>() {
//第一个参数是future的结果,第二个参数是future2的结果
@Override
public String apply(Integer x, String y) {
return y + "-" + x;
}
});*/
try {
System.out.println("thenCombine方法生成的CompletableFuture的结果:" + f.get());
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
输出结果:
thenCombine方法生成的CompletableFuture的结果:abc-100
8、acceptEither/applyToEither
thenAcceptBoth和runAfterBoth是当两个CompletableFuture都计算完成,而下面要了解的方法是当任意一个CompletableFuture计算完成的时候就会执行。
public CompletableFuture<Void> acceptEither(CompletionStage<? extends T> other, Consumer<? super T> action)
public CompletableFuture<Void> acceptEitherAsync(CompletionStage<? extends T> other, Consumer<? super T> action)
public CompletableFuture<Void> acceptEitherAsync(CompletionStage<? extends T> other, Consumer<? super T> action, Executor executor)
public <U> CompletableFuture<U> applyToEither(CompletionStage<? extends T> other, Function<? super T,U> fn)
public <U> CompletableFuture<U> applyToEitherAsync(CompletionStage<? extends T> other, Function<? super T,U> fn)
public <U> CompletableFuture<U> applyToEitherAsync(CompletionStage<? extends T> other, Function<? super T,U> fn, Executor executor)
acceptEither方法是当任意一个CompletionStage完成的时候,action这个消费者就会被执行。这个方法返回CompletableFuture<Void>。
applyToEither方法是当任意一个CompletionStage完成的时候,fn会被执行,它的返回值会当作新的CompletableFuture<U>的计算结果。它的返回值是先执行完的那个CompletableFuture的结果经过fn函数转换之后的结果。
下面这个例子有时会输出100,有时候会输出200,哪个Future先完成就会根据它的结果计算。
public static void eitherTest(){
Random rand = new Random();
CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
try {
Thread.sleep(3000 + rand.nextInt(1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
return 100;
});
CompletableFuture<Integer> future2 = CompletableFuture.supplyAsync(() -> {
try {
Thread.sleep(3000 + rand.nextInt(1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
return 200;
});
CompletableFuture<String> f = future.applyToEither(future2,i -> i.toString());
try {
System.out.println(f.get());
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
9、allOf/anyOf/join
前面我们已经介绍了几个静态方法:completedFuture、runAsync、supplyAsync,下面介绍的这两个方法用来组合多个CompletableFuture,参数是CompletableFuture数组。
public static CompletableFuture<Void> allOf(CompletableFuture<?>... cfs)
public static CompletableFuture<Object> anyOf(CompletableFuture<?>... cfs)
allOf方法是当所有的CompletableFuture都执行完后执行计算。
anyOf方法是当任意一个CompletableFuture执行完后就会执行计算,计算的结果相同。
anyOf和applyToEither不同:anyOf接受任意多的CompletableFuture,但是applyToEither只是判断两个CompletableFuture;anyOf返回值的计算结果是参数中其中一个CompletableFuture的计算结果,applyToEither返回值的计算结果却是要经过fn处理的。
下面的代码运行结果有时是100,有时是"abc"。
public static void anyOfTest(){
Random rand = new Random();
CompletableFuture<Integer> future1 = CompletableFuture.supplyAsync(() -> {
try {
Thread.sleep(3000 + rand.nextInt(1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
return 100;
});
CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> {
try {
Thread.sleep(3000 + rand.nextInt(1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
return "abc";
});
//CompletableFuture<Void> f = CompletableFuture.allOf(future1,future2);
CompletableFuture<Object> f = CompletableFuture.anyOf(future1,future2);
try {
System.out.println(f.get());
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
如果用过Guava的Future类,你就会知道它的Futures辅助类提供了很多便利方法,用来处理多个Future,而不像Java的CompletableFuture,只提供了allOf、anyOf两个方法。 比如有这样一个需求,将多个CompletableFuture组合成一个CompletableFuture,这个组合后的CompletableFuture的计算结果是个List,它包含前面所有的CompletableFuture的计算结果,guava的Futures.allAsList可以实现这样的功能,但是对于java CompletableFuture,我们需要一些辅助方法。
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.function.Function;
public static void allOfTest() {
//不要使用HashMap,会存在并发修改异常
ConcurrentMap<String, Object> map = new ConcurrentHashMap<>();
List<String> list = new ArrayList<>();
list.add("zhangsan");
list.add("lisi");
list.add("wangwu");
/*List<CompletableFuture> futureList =
list.stream().map(new Function<String, CompletableFuture>() {
@Override
public CompletableFuture apply(String s) {
return CompletableFuture.runAsync(new Runnable() {
@Override
public void run() {
try {
System.out.println("异步执行任务,耗时1秒");
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
map.put(s, s.length());
}
});
}
}).collect(Collectors.toList());
CompletableFuture[] futures = futureList.toArray(new CompletableFuture[0]);*/
CompletableFuture[] futures = list.stream().map(new Function<String, CompletableFuture>() {
@Override
public CompletableFuture apply(String s) {
return CompletableFuture.runAsync(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(1000);
System.out.println("异步执行任务,耗时1秒");
} catch (InterruptedException e) {
e.printStackTrace();
}
//在多线程中使用Map时一定要使用ConcurrentHashMap,如果使用List,也要使用同步的List<Object> list = Collections.synchronizedList(new ArrayList<>());
map.put(s, s.length());
}
});
}
}).toArray(CompletableFuture[]::new);
CompletableFuture<Void> allOfFuture = CompletableFuture.allOf(futures);
System.out.println("使用join阻塞主线程,与线程的join用法类似");
allOfFuture.join();
/*CompletableFuture<Object> future = allOfFuture.thenApply(new Function<Void, Object>() {
@Override
public Object apply(Void unused) {
return futureList.stream().map(CompletableFuture::join).collect(Collectors.toList());
}
});
future.join();*/
System.out.println("阻塞结束之后,主线程继续执行");
if (map.size() != 3){
System.out.println("出现错误----------" + map.size());
}
List<String> keys = new ArrayList<>(map.keySet());
for (int i=0;i<keys.size();i++){
System.out.println(keys.get(i) + "===" + map.get(keys.get(i)) + "===" + i);
}
/*for (Map.Entry<String,Object> me : map.entrySet()){
System.out.println(me.getKey() + "========" + me.getValue());
}*/
}
得到的结果如下:
使用join阻塞主线程,与线程的join用法类似
异步执行任务,耗时1秒
异步执行任务,耗时1秒
异步执行任务,耗时1秒
阻塞结束之后,主线程继续执行
lisi===4===0
zhangsan===8===1
wangwu===6===2
根据这些结果来看,主线程在join方法处确实被阻塞了。
10、抛出异常
CompletableFuture处理的异常都会封装成java.util.concurrent.CompletionException抛出,会丢失原有异常类信息。
1、pom.xml文件
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.12.RELEASE</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>multi-thread</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.3.12.RELEASE</version>
</dependency>
</dependencies>
</project>
2、启动类
package com.multi.thread;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class MultiThreadApplication {
public static void main(String[] args) {
SpringApplication.run(MultiThreadApplication.class,args);
}
}
3、自定义异常
package com.multi.thread.exception;
public class CustomerException extends RuntimeException {
private String errCode;
private String errMsg;
public CustomerException(String errMsg){
super(errMsg);
}
public CustomerException(String errCode, String errMsg) {
super(errMsg);
this.errCode = errCode;
}
public String getErrCode() {
return errCode;
}
public String getErrMsg() {
return errMsg;
}
}
4、统一响应实体类
package com.multi.thread.resp;
public class RespBody {
private Integer code;
private String msg;
private Object data;
public RespBody() {
}
public RespBody(String msg) {
this.msg = msg;
}
public RespBody(String msg, Object data) {
this.msg = msg;
this.data = data;
}
public RespBody(Integer code, String msg, Object data) {
this.code = code;
this.msg = msg;
this.data = data;
}
public static RespBody ok(){
return new RespBody(0,"","");
}
public static RespBody ok(String msg){
return new RespBody(0,msg,"");
}
public static RespBody ok(String msg, Object data){
return new RespBody(0,msg,data);
}
public static RespBody fail(String msg){
return new RespBody(-1,msg,"");
}
/*
* 需要为至少一个属性添加getter方法,否则会报如下错误:
* No converter found for return value of type: class com.multi.thread.resp.RespBody
* */
public Integer getCode() {
return code;
}
public String getMsg() {
return msg;
}
public Object getData() {
return data;
}
}
5、统一异常处理器
package com.multi.thread.handler;
import com.multi.thread.exception.CustomerException;
import com.multi.thread.resp.RespBody;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import java.util.concurrent.CompletionException;
@ControllerAdvice
public class UniqueExceptionHandler {
@ExceptionHandler(value = Exception.class)
@ResponseBody
public RespBody handleException(Throwable e){
System.out.println("进入到异常处理器中");
if (e instanceof CustomerException){
System.out.println("customer exception!!!!!");
return RespBody.fail(((CustomerException) e).getErrMsg());
}
if (e instanceof CompletionException){
System.out.println("completion exception!!!!!");
return RespBody.fail(e.getCause().getMessage());
} else {
System.out.println("other exception!!!!!");
return RespBody.fail("other exception");
}
}
}
6、service业务类
package com.multi.thread.service;
import com.multi.thread.exception.CustomerException;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.function.Function;
import java.util.stream.Collectors;
@Service
public class CompletableFutureService {
public void comFutureTest(){
List<Integer> list = new ArrayList<>();
for (int i=1;i<=10;i++){
list.add(i);
}
//必须要有返回值,否则异常不会被抛出,就不能进入到异常处理器中
List<CompletableFuture<Void>> futures = list.stream().map(
new Function<Integer, CompletableFuture<Void>>() {
@Override
public CompletableFuture<Void> apply(Integer i) {
return CompletableFuture.runAsync(() -> {
if (i % 5 == 0){
String threadName = Thread.currentThread().getName();
System.out.println("当前线程===" + threadName + ",被5整除了==========" + i);
throw new CustomerException("5000","被5整除了==========" + i);
} else if (i % 4 == 0){
String threadName = Thread.currentThread().getName();
System.out.println("当前线程===" + threadName + ",被4整除了==========" + i);
throw new CustomerException("4000","被4整除了==========" + i);
}
});
}
}
).collect(Collectors.toList());
CompletableFuture.allOf(futures.toArray(new CompletableFuture[]{})).join();
//第二种写法
/*List<CompletableFuture<Void>> futureList = new ArrayList<>();
list.forEach(new Consumer<Integer>() {
@Override
public void accept(Integer i) {
futureList.add(CompletableFuture.runAsync(() -> {
if (i % 5 == 0){
String threadName = Thread.currentThread().getName();
System.out.println("当前线程===" + threadName + ",被5整除了==========" + i);
throw new CustomerException("5000","被5整除了==========" + i);
} else if (i % 4 == 0){
String threadName = Thread.currentThread().getName();
System.out.println("当前线程===" + threadName + ",被4整除了==========" + i);
throw new CustomerException("4000","被4整除了==========" + i);
}
}));
}
});
CompletableFuture.allOf(futureList.toArray(new CompletableFuture[0])).join();*/
}
/*public static void main(String[] args) {
CompletableFutureTest futureTest = new CompletableFutureTest();
futureTest.comFutureTest();
}*/
}
7、controller接口类
package com.multi.thread.controller;
import com.multi.thread.service.CompletableFutureService;
import com.multi.thread.resp.RespBody;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping(value = "/future")
public class CompletableFutureController {
@Autowired
private CompletableFutureService futureTest;
@GetMapping(value = "/test")
public RespBody test(){
futureTest.comFutureTest();
return RespBody.ok("主线程结束");
}
}
8、浏览器访问http://localhost:8080/future/test进行测试
根据控制台结果来看,在CompletableFuture中抛出的异常CustomerException被封装成CompletionException(继承RuntimeException),在统一异常处理器中需要加上对异常类型是否为CompletionException的判断,否则封装后抛出的CompletionException异常不能被拦截处理。
11、异常处理
接着上面样例,创建service:
package com.multi.thread.service;
import com.multi.thread.exception.CustomerException;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.function.BiFunction;
import java.util.stream.Collectors;
@Service
public class HandleExceptionService {
public void exceptionTest(){
List<Integer> list = new ArrayList<>();
for (int i=1;i<10;i++){
list.add(i);
}
long start = System.currentTimeMillis();
//methodOne方法耗时11秒左右,时间由最长耗时的那个线程决定,
//说明即使某一个线程发生了异常, 也要等待所有的线程执行完才返回到主线程中
//methodOne(list);
//某个线程出现异常,主线程不再等待其他后续任务,主线程继续向下执行,其他任务继续在线程池中执行
methodTwo(list);
long end = System.currentTimeMillis();
System.out.println("主线程结束,执行耗时(s)=====" + (end-start)/1000);
}
public void methodOne(List<Integer> list){
StringBuffer sb = new StringBuffer();
List<CompletableFuture<Void>> futureList = list.stream().map(i -> {
return CompletableFuture.runAsync(() -> {
try {
//模拟业务耗时
Thread.sleep(i * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (i % 3 == 0) {
String threadName = Thread.currentThread().getName();
System.out.println("当前线程==" + threadName + ",被3整除了==" + i);
throw new CustomerException("5000", "被3整除了==" + i);
} else {
String threadName = Thread.currentThread().getName();
System.out.println("当前线程==" + threadName + ",i==" + i);
}
}).handle(new BiFunction<Void, Throwable, Void>() {
@Override
public Void apply(Void result, Throwable e) {
if (e != null){
System.out.println("handle异常=====" + e);
sb.append(e.getCause().getMessage()).append("//");
throw new RuntimeException(e.getCause().getMessage());
}
return result;
}
});
}).collect(Collectors.toList());
CompletableFuture.allOf(futureList.toArray(new CompletableFuture[0])).join();
System.out.println("CompletableFuture处理异常====" + sb.toString());
}
public void methodTwo(List<Integer> list){
StringBuffer sb = new StringBuffer();
List<CompletableFuture<Void>> futureList = list.stream().map(i -> {
return CompletableFuture.runAsync(() -> {
try {
//模拟业务耗时
Thread.sleep(i * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (i % 3 == 0) {
String threadName = Thread.currentThread().getName();
System.out.println("当前线程==" + threadName + ",被3整除了==" + i);
throw new CustomerException("5000", "被3整除了==" + i);
} else {
String threadName = Thread.currentThread().getName();
System.out.println("当前线程==" + threadName + ",i==" + i);
}
}).handle(new BiFunction<Void, Throwable, Void>() {
@Override
public Void apply(Void result, Throwable e) {
if (e != null){
System.out.println("handle异常=====" + e);
sb.append(e.getCause().getMessage()).append("//");
throw new RuntimeException(e.getCause().getMessage());
}
return result;
}
});
}).collect(Collectors.toList());
futureList.stream().map(CompletableFuture::join).collect(Collectors.toList());
}
}
在controller中调用service:
package com.multi.thread.controller;
import com.multi.thread.service.CompletableFutureService;
import com.multi.thread.resp.RespBody;
import com.multi.thread.service.HandleExceptionService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping(value = "/future")
public class CompletableFutureController {
@Autowired
private CompletableFutureService futureTest;
@Autowired
private HandleExceptionService handleExceptionService;
@GetMapping(value = "/test")
public RespBody test(){
futureTest.comFutureTest();
return RespBody.ok("主线程结束");
}
@GetMapping(value = "/test2")
public RespBody test2(){
handleExceptionService.exceptionTest();
return RespBody.ok("test2");
}
}