簡體   English   中英

如何強制 CompletableFuture.thenApply() 在運行前一階段的同一線程上運行?

[英]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。 即使是打印語句也可能會減慢供應商的速度,以使主線程首先調用thenApplythenAccept 但這不是可靠的行為,重復運行代碼時可能會得到不同的結果。

未來不僅不記得哪個線程完成了它,也沒有辦法告訴任意線程執行特定代碼。 該線程可能正忙於其他事情,完全不合作,甚至在此期間已經終止。

只考慮

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();

我們怎么能期望傳遞給thenApplythenAccept的函數在已經終止的池的工作線程中執行?

我們也可以寫

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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM