前几天看博客说默认情况下一个CompletableFuture对象对应的异步任务和在同一个CompletableFuture对象上添加的回调事件都是使用ForkJoinPool.commonPool()中的同一个线程来执行的且由于是栈结构,在同一个CompletableFuture对象上行为注册的顺序与行为执行的顺序是相反的。
代码验证
public class CompletableTest {
private static final Integer AVALIABLE_PROCESSORS = Runtime.getRuntime().availableProcessors();
private static final ThreadPoolExecutor THREAD_POOL_EXECUTOR = new ThreadPoolExecutor(AVALIABLE_PROCESSORS,
AVALIABLE_PROCESSORS * 2, 1 , TimeUnit.MINUTES, new LinkedBlockingQueue<>(5), new ThreadPoolExecutor.AbortPolicy());
public static void main(String[] args) throws ExecutionException, InterruptedException, TimeoutException {
CompletableFuture future = CompletableFuture.supplyAsync(() -> {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("0" + Thread.currentThread().getName());
return "1";
},THREAD_POOL_EXECUTOR);
CompletableFuture future1 = future.thenApply(new Function<String, Integer>() {
@Nullable
@Override
public Integer apply(@Nullable String o) {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("1" + Thread.currentThread().getName());
return Integer.valueOf(o);
}
});
CompletableFuture<Integer> future2 = future.thenApply(new Function<String, Integer>() {
@Nullable
@Override
public Integer apply(@Nullable String o) {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("2" + Thread.currentThread().getName());
return Integer.valueOf(o);
}
});
CompletableFuture<Integer> future3 = future.thenApply(new Function<String, Integer>() {
@Nullable
@Override
public Integer apply(@Nullable String o) {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("3" + Thread.currentThread().getName());
return Integer.valueOf(o);
}
});
CompletableFuture<Integer> future4 = future.thenApply(new Function<String, Integer>() {
@Nullable
@Override
public Integer apply(@Nullable String o) {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("4" + Thread.currentThread().getName());
return Integer.valueOf(o);
}
});
CompletableFuture<String> future5 = future3.thenApply(new Function<Integer, String>() {
@Nullable
@Override
public String apply(@Nullable Integer o) {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("5" + Thread.currentThread().getName());
return o.toString();
}
});
Long now = System.currentTimeMillis();
System.out.println(future.get());
System.out.println(System.currentTimeMillis() - now);
System.out.println(future1.get());
System.out.println(System.currentTimeMillis() - now);
System.out.println(future2.get());
System.out.println(System.currentTimeMillis() - now);
System.out.println(future3.get());
System.out.println(System.currentTimeMillis() - now);
System.out.println(future4.get());
System.out.println(System.currentTimeMillis() - now);
System.out.println(future5.get());
System.out.println(System.currentTimeMillis() - now);
}
}
测试结果
0pool-1-thread-1
4pool-1-thread-1
3main
2pool-1-thread-1
5main
1pool-1-thread-1
测试结果怎么会用到了主线程main,明显不符合上述执行异步线程和回调都由同一个线程来处理的说法。
结论
1.如果CompletableFuture对象对应的异步任务再主线程中进行get() 操作,则其对应的多个回调中某些回调将由主线程进行处理,与CompletableFuture对象对应的异步任务的线程不是同一个。
如果在其他线程中调用CompletableFuture对象对应的异步任务的get() 方法,则会将此线程作为可执行其回调的线程之一。
实验:
通过主线程/其他线程在异步任务执行完后调用以下三个方法做对比:
public T getNow(T valueIfAbsent) {
Object r;
return ((r = result) == null) ? valueIfAbsent : reportJoin(r);
}
public T get() throws InterruptedException, ExecutionException {
Object r;
return reportGet((r = result) == null ? waitingGet(true) : r);
}
public void obtrudeValue(T value) {
result = (value == null) ?NIL: value;
postComplete();
}
排查与被volatile修饰的result属性无关,目前测试结果可能和postComplete方法有关;
解释:
在CompletableFuture对象对应的异步任务执行后,则会调用postComplete中调用uniApply执行回调,但是如果主线程调用get()等带有postComplete的方法并且异步任务执行执行已经完成,则会由主线程参与调用回调。
2.如果CompletableFuture对象对应的异步任务是立即完成的(将上述代码sleep(2000)去掉),那么其回调将在主线程中进行处理,也会导致主线程阻塞。
猜测的原因:异步任务执行结束时还未把回调放入栈中,导致回调只能被主线程所执行。
导致的后果:如果由主线程进行处理的某些回调比较耗时,那么会造成主线程阻塞,所以尽量使用thenApplyAsync等方法。
如果如果CompletableFuture对象对应的异步任务再主线程中进行get() 操作在主线程进行另外操作之前,则导致主线程被回调占用,导致主线程正常流程无法运行,使用thenApplyAsync等方法不会出现此问题。