[英]How to force CompletableFuture.thenApply() to run on the same thread that ran the previous stage?
這是我面臨的問題的簡短代碼版本:
public static void main(String[] args) {
CompletableFuture.supplyAsync(() -> {
/*
try {
Thread.sleep(2000);
} catch (InterruptedException ignored) {}
*/
//System.out.println("supplyAsync: " + Thread.currentThread().getName());
return 1;
})
.thenApply(i -> {
System.out.println("apply: " + Thread.currentThread().getName());
return i + 1;
})
.thenAccept((i) -> {
System.out.println("accept: " + Thread.currentThread().getName());
System.out.println("result: " + i);
}).join();
}
這是我得到的 output:
apply: main
accept: main
result: 2
我很驚訝在那里看到main
! 當我取消注釋Thread.sleep()
調用,甚至取消注釋單個sysout
語句時,我預計會發生這樣的事情:
supplyAsync: ForkJoinPool.commonPool-worker-1
apply: ForkJoinPool.commonPool-worker-1
accept: ForkJoinPool.commonPool-worker-1
result: 2
我了解thenApplyAsync()
將確保它不會在main
線程上運行,但我想避免將供應商返回的數據從運行supplyAsync
的線程傳遞到將要運行thenApply
的線程和另一個后續then
s在鏈中。
thenApply
方法在調用者的線程中評估 function,因為未來已經完成。 當然,當你在supplier中插入一個sleep
,此時future還沒有完成, thenApply
就會調用Apply。 即使是打印語句也可能會減慢供應商的速度,以使主線程首先調用thenApply
和thenAccept
。 但這不是可靠的行為,重復運行代碼時可能會得到不同的結果。
未來不僅不記得哪個線程完成了它,也沒有辦法告訴任意線程執行特定代碼。 該線程可能正忙於其他事情,完全不合作,甚至在此期間已經終止。
只考慮
ExecutorService s = Executors.newSingleThreadExecutor();
CompletableFuture<Integer> cf = CompletableFuture.supplyAsync(() -> {
System.out.println("supplyAsync: " + Thread.currentThread().getName());
return 1;
}, s);
s.shutdown();
s.awaitTermination(1, TimeUnit.DAYS);
cf.thenApply(i -> {
System.out.println("apply: " + Thread.currentThread().getName());
return i + 1;
})
.thenAccept((i) -> {
System.out.println("accept: " + Thread.currentThread().getName());
System.out.println("result: " + i);
}).join();
我們怎么能期望傳遞給thenApply
和thenAccept
的函數在已經終止的池的工作線程中執行?
我們也可以寫
CompletableFuture<Integer> cf = new CompletableFuture<>();
Thread t = new Thread(() -> {
System.out.println("completing: " + Thread.currentThread().getName());
cf.complete(1);
});
t.start();
t.join();
System.out.println("completer: " + t.getName() + " " + t.getState());
cf.thenApply(i -> {
System.out.println("apply: " + Thread.currentThread().getName());
return i + 1;
})
.thenAccept((i) -> {
System.out.println("accept: " + Thread.currentThread().getName());
System.out.println("result: " + i);
}).join();
這將打印類似的東西
completing: Thread-0
completer: Thread-0 TERMINATED
apply: main
accept: main
result: 2
顯然,我們不能堅持讓這個線程處理后續階段。
但是,即使線程是池中仍然活着的工作線程,它也不知道它已經完成了未來,也沒有“處理后續階段”的概念。 在Executor
抽象之后,它剛剛從隊列中接收到一個任意的Runnable
,在處理它之后,它繼續其主循環,從隊列中獲取下一個Runnable
。
因此,一旦第一個 future 完成,告訴它完成其他 future 的工作的唯一方法就是將任務排入隊列。 當使用thenApplyAsync
指定相同的池或使用…Async
方法執行所有操作時會發生這種情況,而無需執行器,即使用默認池。
當您對所有…Async
方法使用單線程執行器時,您可以確保所有操作都由同一個線程執行,但它們仍將通過池的隊列。 從那時起,在已經完成的未來的情況下,實際上是主線程將相關操作排入隊列,線程安全隊列以及因此的同步開銷是不可避免的。
但請注意,即使您首先設法創建依賴操作鏈,在單個工作線程按順序處理它們之前,這種開銷仍然存在。 每個未來的完成都是通過以線程安全的方式存儲新的 state 來完成的,使結果可能對所有其他線程可見,並以原子方式檢查同時是否發生了並發完成(例如取消)。 然后,由其他線程鏈接的相關操作將在執行之前以線程安全的方式被獲取。
所有這些具有同步語義的操作使得在具有依賴鏈的CompletableFuture
時不太可能有由同一線程處理數據的好處。
進行可能具有性能優勢的實際本地處理的唯一方法是使用
CompletableFuture.runAsync(() -> {
System.out.println("supplyAsync: " + Thread.currentThread().getName());
int i = 1;
System.out.println("apply: " + Thread.currentThread().getName());
i = i + 1;
System.out.println("accept: " + Thread.currentThread().getName());
System.out.println("result: " + i);
}).join();
或者,換句話說,如果您不想要分離處理,請不要首先創建分離處理階段。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.