Java的线程不只是Thread,调度不止是.start、.join、.sleep。
jdk5引入Future、Callable,多线程调度有了很多高级应用。JDK8开始之后引入lambda,程序调度流程粒度进一步缩小,配合新的线程相关API,出现了很多看起来优雅,功能强大,使用要求高的线程调度用法。
CompletableFuture 方法探析。
相关方法
public CompletableFuture thenApply(Function super T,? extends U> fn
public CompletableFuture thenApplyAsync(Function super T,? extends U> fn)
public CompletableFuture thenApplyAsync(Function super T,? extends U> fn, Executor executor)
CompletableFuture的静态方法,接受一个Function生成一个新的CompletableFuture对象,对象生成时异步执行传入的Function;< U >泛型为接受的Function的返回值类型。
查看源码如下:
public static CompletableFuture supplyAsync(Supplier supplier) {
return asyncSupplyStage(asyncPool, supplier);
}
指定默认的线程池asyncPool调用了asyncSupplyStage方法返回CompletableFuture,asyncPool是CompletableFuture私有静态属性。supplyAsync有一个重载方法,可以指定执行的连接池。【supplyAsync(Supplier< U > supplier, Executor executor)】
关于asyncSupplyStage方法如下:
static CompletableFuture asyncSupplyStage(Executor e,Supplier f){
if (f == null) throw new NullPointerException();
CompletableFuture d = new CompletableFuture();
e.execute(new AsyncSupply(d, f));
return d;
}
初始化一个CompletableFuture< U >对象,和传入的Function一同包装成一个AsyncSupply对象,根据继承关系,AsyncSupply是Runnable接口的实现类。在AsyncSupply重写run方法,作用就是将Fanction结果的返回值设置到CompletableFuture对象中,完成后继续下一个任务。
public void run() {
CompletableFuture d; Supplier 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]
相关方法
public CompletionStage thenApply(Function super T,? extends U> fn);
public CompletionStage thenApplyAsync(Function super T,? extends U> fn);
public CompletionStage thenApplyAsync(Function super T,? extends U> fn,Executor executor);
使用示例:
@Test
public void thenApply() throws InterruptedException, ExecutionException {
CompletableFuture 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
相关方法
public CompletionStage thenAccept(Consumer super T> action);
public CompletionStage thenAcceptAsync(Consumer super T> action);
public CompletionStage 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方法是有返回值的。
相关方法
public CompletionStage thenRun(Runnable action);
public CompletionStage thenRunAsync(Runnable action);
public CompletionStage 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应用的是同一个。
相关方法:
public CompletionStage thenCombine(CompletionStage extends U> other,BiFunction super T,? super U,? extends V> fn);
public CompletionStage thenCombineAsync(CompletionStage extends U> other,BiFunction super T,? super U,? extends V> fn);
public CompletionStage 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的入参,在同一个线程中进行。
相关方法:
public CompletionStage thenAcceptBoth(CompletionStage extends U> other,BiConsumer super T, ? super U> action);
public CompletionStage thenAcceptBothAsync(CompletionStage extends U> other,BiConsumer super T, ? super U> action);
public CompletionStage thenAcceptBothAsync(CompletionStage extends U> other,BiConsumer super T, ? super U> action, Executor executor);
thenCombine的无返回值版本。
相关方法:
public CompletionStage runAfterBoth(CompletionStage> other,Runnable action);
public CompletionStage runAfterBothAsync(CompletionStage> other,Runnable action);
public CompletionStage runAfterBothAsync(CompletionStage> other,Runnable action,Executor executor);
thenAcceptBothAsync的无入参版。不关注上一个和other的执行结果。
相关方法:
public <U> CompletionStage<U> applyToEither(CompletionStage extends T> other,Function super T, U> fn);
public CompletionStage applyToEitherAsync(CompletionStage extends T> other,Function super T, U> fn);
public CompletionStage 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
同样的,applyToEither的Void版acceptEither和无参Void版runAfterEither。
相关方法:
public CompletionStage acceptEither(CompletionStage extends T> other,Consumer super T> action);
public CompletionStage acceptEitherAsync(CompletionStage extends T> other,Consumer super T> action);
public CompletionStage acceptEitherAsync(CompletionStage extends T> other,Consumer super T> action,Executor executor);
public CompletionStage runAfterEither(CompletionStage> other,Runnable action);
public CompletionStage runAfterEitherAsync(CompletionStage> other,Runnable action);
public CompletionStage runAfterEitherAsync(CompletionStage> other,Runnable action,Executor executor);
相关方法
public CompletionStage exceptionally(Function 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
从打印线程信息看,异常处理部分的方法执行和发生异常的代码在同一个线程中执行。
相关方法:
public CompletionStage whenComplete(BiConsumer super T, ? super Throwable> action);
public CompletionStage whenCompleteAsync(BiConsumer super T, ? super Throwable> action);
public CompletionStage 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只是将前一个的执行结果,无论是异常还是正常结果,传递到下面去。
相关方法:
public CompletionStage handle(BiFunction super T, Throwable, ? extends U> fn);
public CompletionStage handleAsync(BiFunction super T, Throwable, ? extends U> fn);
public CompletionStage 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类的泛型和流式调用设计,在复杂业务的任务流程调用上有一定的便利。
参考:
简书:数齐—CompletableFuture 详解