Java的线程不只是Thread,调度不止是.start、.join、.sleep。
jdk5引入Future、Callable,多线程调度有了很多高级应用。JDK8开始之后引入lambda,程序调度流程粒度进一步缩小,配合新的线程相关API,出现了很多看起来优雅,功能强大,使用要求高的线程调度用法。
CompletableFuture 方法探析。
1. supplyAsync
相关方法
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的静态方法,接受一个Function生成一个新的CompletableFuture对象,对象生成时异步执行传入的Function;< U >泛型为接受的Function的返回值类型。
查看源码如下:
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier) {
return asyncSupplyStage(asyncPool, supplier);
}
指定默认的线程池asyncPool调用了asyncSupplyStage方法返回CompletableFuture,asyncPool是CompletableFuture私有静态属性。supplyAsync有一个重载方法,可以指定执行的连接池。【supplyAsync(Supplier< U > supplier, Executor executor)】
关于asyncSupplyStage方法如下:
static <U> CompletableFuture<U> asyncSupplyStage(Executor e,Supplier<U> f){
if (f == null) throw new NullPointerException();
CompletableFuture<U> d = new CompletableFuture<U>();
e.execute(new AsyncSupply<U>(d, f));
return d;
}
初始化一个CompletableFuture< U >对象,和传入的Function一同包装成一个AsyncSupply对象,根据继承关系,AsyncSupply是Runnable接口的实现类。在AsyncSupply重写run方法,作用就是将Fanction结果的返回值设置到CompletableFuture对象中,完成后继续下一个任务。
public void run() {
CompletableFuture<T> d; Supplier<T> f;
if ((d = dep) != null && (f = fn) != null) {
dep = null; fn = null;
if (d.result == null) {
try {
d.completeValue(f.get());
} catch (Throwable ex) {
d.completeThrowable(ex);
}
}
d.postComplete();
}
}
综上所知:asyncSupplyStage方法中e.execute执行时会初始化一个Thread执行操作,传入的Function是异步执行不阻塞主线程。测试代码如下:
@Test
public void supplyAsync() {
CompletableFuture.supplyAsync(() -> {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("1 Say hello!" + Thread.currentThread());
return "hello";
});
CompletableFuture.supplyAsync(() -> {
System.out.println("2 Say hello!" + Thread.currentThread());
return "hello";
});
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
运行结果:.
2 Say hello!Thread[ForkJoinPool.commonPool-worker-2,5,main]
1 Say hello!Thread[ForkJoinPool.commonPool-worker-1,5,main]
2.thenApplyAsync,变换
相关方法
public <U> CompletionStage<U> thenApply(Function<? super T,? extends U> fn);
public <U> CompletionStage<U> thenApplyAsync(Function<? super T,? extends U> fn);
public <U> CompletionStage<U> thenApplyAsync(Function<? super T,? extends U> fn,Executor executor);
使用示例:
@Test
public void thenApply() throws InterruptedException, ExecutionException {
CompletableFuture<String> supplyAsync = CompletableFuture.supplyAsync(() -> "12");
CompletableFuture<Integer> thenApply = supplyAsync.thenApplyAsync(s -> {
System.out.println("String:" + s);
return Integer.valueOf(s);
});
Integer result = thenApply.get();
System.out.println("int:" + result);
}
方法的入参fn是一个函数式接口,这个fn的入参是上一步的计算结果“12”,get()会阻塞主线程等待执行结果返回,最终打印结果:
String:12
int:12
3.thenAcceptAsync,消耗
相关方法
public CompletionStage<Void> thenAccept(Consumer<? super T> action);
public CompletionStage<Void> thenAcceptAsync(Consumer<? super T> action);
public CompletionStage<Void> thenAcceptAsync(Consumer<? super T> action,Executor executor);
从方法名含义上理解,接受上一个执行结果,应用一个没有返回值(Void)的Consumer。
测试用例:
@Test
public void thenAccept() {
CompletableFuture.supplyAsync(() -> "hello").thenAcceptAsync(s -> {
System.out.println(s + " world");
});
}
虽然入参的Consumer对象是不期待返回值的,但是thenAcceptAsync方法是有返回值的。
4.thenRunAsync
相关方法
public CompletionStage<Void> thenRun(Runnable action);
public CompletionStage<Void> thenRunAsync(Runnable action);
public CompletionStage<Void> thenRunAsync(Runnable action,Executor executor);
在上一步执行结束之后,启动的一个线程,入参是一个Runnable类型的,不需要入参和返回值。和thenAccept相比,不需要之前执行结果的依赖。
测试方法
@Test
public void thenRun() {
CompletableFuture.supplyAsync(() -> {
try {
System.out.println(Thread.currentThread());
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "hello";
}).thenRun(() -> {
System.out.println("hello world in " + Thread.currentThread());
System.exit(1);
});
while (true) {
System.out.println("while");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
输出结果:
Thread[ForkJoinPool.commonPool-worker-1,5,main]
while
while
while
while
while
while
while
while
while
while
hello world in Thread[ForkJoinPool.commonPool-worker-1,5,main]
从结果上看,Runnable 使用的线程池和supplyAsync应用的是同一个。
5.thenCombine,结合
相关方法:
public <U,V> CompletionStage<V> thenCombine(CompletionStage<? extends U> other,BiFunction<? super T,? super U,? extends V> fn);
public <U,V> CompletionStage<V> thenCombineAsync(CompletionStage<? extends U> other,BiFunction<? super T,? super U,? extends V> fn);
public <U,V> CompletionStage<V> thenCombineAsync(CompletionStage<? extends U> other,BiFunction<? super T,? super U,? extends V> fn,Executor executor);
获取上一个的返回结果,结合other的返回结果,两个结果作为参数共同传入BiFunction中计算一个最终结果。
测试方法:
@Test
public void thenCombine() {
String result = CompletableFuture.supplyAsync(() -> {
try {
System.out.println("hello in " + Thread.currentThread());
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "hello";
}).thenCombine(CompletableFuture.supplyAsync(() -> {
try {
System.out.println("world in " + Thread.currentThread());
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "world";
}), (s1, s2) -> {
System.out.println("hello world in " + Thread.currentThread());
return s1 + " " + s2;
}).join();
System.out.println("main get result:"+result);
}
执行结果:
hello in Thread[ForkJoinPool.commonPool-worker-1,5,main]
world in Thread[ForkJoinPool.commonPool-worker-2,5,main]
hello world in Thread[ForkJoinPool.commonPool-worker-2,5,main]
main get result:hello world
分任务在不同的线程里并行执行,第二个字任务和汇总任务因为都是thenCombine的入参,在同一个线程中进行。
6.thenAcceptBothAsync
相关方法:
public <U> CompletionStage<Void> thenAcceptBoth(CompletionStage<? extends U> other,BiConsumer<? super T, ? super U> action);
public <U> CompletionStage<Void> thenAcceptBothAsync(CompletionStage<? extends U> other,BiConsumer<? super T, ? super U> action);
public <U> CompletionStage<Void> thenAcceptBothAsync(CompletionStage<? extends U> other,BiConsumer<? super T, ? super U> action, Executor executor);
thenCombine的无返回值版本。
7.runAfterBoth
相关方法:
public CompletionStage<Void> runAfterBoth(CompletionStage<?> other,Runnable action);
public CompletionStage<Void> runAfterBothAsync(CompletionStage<?> other,Runnable action);
public CompletionStage<Void> runAfterBothAsync(CompletionStage<?> other,Runnable action,Executor executor);
thenAcceptBothAsync的无入参版。不关注上一个和other的执行结果。
8.applyToEither,选一个快的
相关方法:
public <U> CompletionStage<U> applyToEither(CompletionStage<? extends T> other,Function<? super T, U> fn);
public <U> CompletionStage<U> applyToEitherAsync(CompletionStage<? extends T> other,Function<? super T, U> fn);
public <U> CompletionStage<U> applyToEitherAsync(CompletionStage<? extends T> other,Function<? super T, U> fn,Executor executor);
测试方法:
@Test
public void applyToEither() {
String result = CompletableFuture.supplyAsync(() -> {
int sleep = (int)(Math.random()*10);
System.out.println(String.format("hello sleep %s s", sleep));
try {
Thread.sleep(sleep*1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "hello";
}).applyToEither(CompletableFuture.supplyAsync(() -> {
int sleep = (int)(Math.random()*10);
System.out.println(String.format("hello world %s s", sleep));
try {
Thread.sleep(sleep*1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "world";
}), s -> s).join();
System.out.println("main get result:"+result);
}
整体逻辑是,最终方法需要一个入参去计算结果,获得这个参数有两种途径,哪个快就用哪个的计算结果作为参数计算最终结果,慢的被丢弃。
测试输出结果:
hello sleep 9 s
hello world 3 s
main get result:world
9.acceptEither & runAfterEither
同样的,applyToEither的Void版acceptEither和无参Void版runAfterEither。
相关方法:
public CompletionStage<Void> acceptEither(CompletionStage<? extends T> other,Consumer<? super T> action);
public CompletionStage<Void> acceptEitherAsync(CompletionStage<? extends T> other,Consumer<? super T> action);
public CompletionStage<Void> acceptEitherAsync(CompletionStage<? extends T> other,Consumer<? super T> action,Executor executor);
public CompletionStage<Void> runAfterEither(CompletionStage<?> other,Runnable action);
public CompletionStage<Void> runAfterEitherAsync(CompletionStage<?> other,Runnable action);
public CompletionStage<Void> runAfterEitherAsync(CompletionStage<?> other,Runnable action,Executor executor);
10. exceptionally,异常补偿
相关方法
public CompletionStage<T> exceptionally(Function<Throwable, ? extends T> fn);
代码发生异常时,进行补偿,类似try-catch的功能。
测试方法:
@Test
public void exceptionally() {
String result = CompletableFuture.supplyAsync(() -> {
try {
System.out.println(Thread.currentThread());
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (1 == 1) {
throw new RuntimeException("测试一下异常情况");
}
return "s1";
}).exceptionally(e -> {
System.out.println(Thread.currentThread());
System.out.println(e.getMessage());
return "hello world";
}).join();
System.out.println(result);
}
输出结果:
Thread[ForkJoinPool.commonPool-worker-1,5,main]
Thread[ForkJoinPool.commonPool-worker-1,5,main]
java.lang.RuntimeException: 测试一下异常情况
hello world
从打印线程信息看,异常处理部分的方法执行和发生异常的代码在同一个线程中执行。
11. whenComplete 结束记录。
相关方法:
public CompletionStage<T> whenComplete(BiConsumer<? super T, ? super Throwable> action);
public CompletionStage<T> whenCompleteAsync(BiConsumer<? super T, ? super Throwable> action);
public CompletionStage<T> whenCompleteAsync(BiConsumer<? super T, ? super Throwable> action,Executor executor);
接收执行完成的结果,返回值或异常,不会影响最终执行结果。
与exceptionally进行比较,测试代码:
@Test
public void whenComplete() {
String result = CompletableFuture.supplyAsync(() -> {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "s1";
}).whenComplete((s, t) -> {
System.out.println(s);
System.out.println(t);
int i = 1/0;
}).exceptionally(e -> {
System.out.println(e.getMessage());
return "hello world";
}).join();
System.out.println(result);
}
输出测试结果:
s1
null
java.lang.ArithmeticException: / by zero
hello world
注意whenComplete中参数的空指针问题。
另一种情况:
@Test
public void whenComplete() {
String result = CompletableFuture.supplyAsync(() -> {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}int i = 1/0;
return "s1";
}).whenComplete((s, t) -> {
System.out.println(s);
System.out.println(t);
}).exceptionally(e -> {
System.out.println(e.getMessage());
return "hello world";
}).join();
System.out.println(result);
}
执行结果:
null
java.util.concurrent.CompletionException: java.lang.ArithmeticException: / by zero
java.lang.ArithmeticException: / by zero
hello world
可见,whenComplete只是将前一个的执行结果,无论是异常还是正常结果,传递到下面去。
12.handle,结果处理
相关方法:
public <U> CompletionStage<U> handle(BiFunction<? super T, Throwable, ? extends U> fn);
public <U> CompletionStage<U> handleAsync(BiFunction<? super T, Throwable, ? extends U> fn);
public <U> CompletionStage<U> handleAsync(BiFunction<? super T, Throwable, ? extends U> fn,Executor executor);
所谓结果,包含两种,一种是喜闻乐见的正常结果,另一种是“喜闻乐见”的异常结果。
发生异常测试:
@Test
public void handle() {
String result = CompletableFuture.supplyAsync(() -> {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//出现异常
int i =1/0;
return "s1";
}).handle((s, t) -> {
if (t != null) {
return "s2";
}
return s;
}).join();
System.out.println(result);
}
执行结果:
s2
无异常测试:
@Test
public void handle() {
String result = CompletableFuture.supplyAsync(() -> {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "s1";
}).handle((s, t) -> {
if (t != null) {
return "s2";
}
return s;
}).join();
System.out.println(result);
}
结果:
s1
对比whenComplete,它功能更加强大,增加了可以影响最终结果的功能。
以上只是简单的单个或两个的结合方法的测试,在实际的业务中所需处理的逻辑只会更加复杂。CompletableFuture类的泛型和流式调用设计,在复杂业务的任务流程调用上有一定的便利。