[英]CompletableFuture#whenComplete not called if thenApply is used
[英]How to prevent CompletableFuture#whenComplete execution in context thread
我有以下代碼:
ConcurrentHashMap taskMap= new ConcurrentHashMap();
....
taskMap.compute(key, (k, queue) -> {
CompletableFuture<Void> future = (queue == null)
? CompletableFuture.runAsync(myTask, poolExecutor)
: queue.whenCompleteAsync((r, e) -> myTask.run(), poolExecutor);
//to prevent OutOfMemoryError in case if we will have too much keys
future.whenComplete((r, e) -> taskMap.remove(key, future));
return future;
});
此代碼的問題是future
在whenComplete
函數參數與compute
調用相同的線程中調用時已經完成的情況。 在此方法的主體中,我們從地圖中刪除條目。 但是計算方法文檔禁止這樣做,並且應用程序凍結。
如何解決此問題?
最明顯的解決方案是使用whenCompleteAsync
而不是whenComplete
,因為前者保證使用提供的Executor
而不是調用線程來執行操作。 可以證明
Executor ex = r -> { System.out.println("job scheduled"); new Thread(r).start(); };
for(int run = 0; run<2; run++) {
boolean completed = run==0;
System.out.println("*** "+(completed? "with already completed": "with async"));
CompletableFuture<String> source = completed?
CompletableFuture.completedFuture("created in "+Thread.currentThread()):
CompletableFuture.supplyAsync(() -> {
LockSupport.parkNanos(TimeUnit.SECONDS.toNanos(1));
return "created in "+Thread.currentThread();
}, ex);
source.thenApplyAsync(s -> s+"\nprocessed in "+Thread.currentThread(), ex)
.whenCompleteAsync((s,t) -> {
if(t!=null) t.printStackTrace(); else System.out.println(s);
System.out.println("consumed in "+Thread.currentThread());
}, ex)
.join();
}
這將打印類似
*** with already completed
job scheduled
job scheduled
created in Thread[main,5,main]
processed in Thread[Thread-0,5,main]
consumed in Thread[Thread-1,5,main]
*** with async
job scheduled
job scheduled
job scheduled
created in Thread[Thread-2,5,main]
processed in Thread[Thread-3,5,main]
consumed in Thread[Thread-4,5,main]
所以你可以用
taskMap.compute(key, (k, queue) -> {
CompletableFuture<Void> future = (queue == null)
? CompletableFuture.runAsync(myTask, poolExecutor)
: queue.whenCompleteAsync((r, e) -> myTask.run(), poolExecutor);
//to prevent OutOfMemoryError in case if we will have too much keys
future.whenCompleteAsync((r, e) -> taskMap.remove(key, future), poolExecutor);
return future;
});
如果提前完成的可能性很大,則可以使用以下方法減少開銷:
taskMap.compute(key, (k, queue) -> {
CompletableFuture<Void> future = (queue == null)
? CompletableFuture.runAsync(myTask, poolExecutor)
: queue.whenCompleteAsync((r, e) -> myTask.run(), poolExecutor);
//to prevent OutOfMemoryError in case if we will have too much keys
if(future.isDone()) future = null;
else future.whenCompleteAsync((r, e) -> taskMap.remove(key, future), poolExecutor);
return future;
});
也許您沒有想到這個顯而易見的解決方案,因為您不希望將依賴操作始終作為新任務調度到池中,即使完成已經發生在另一個任務中也是如此。 您可以使用專門的執行程序解決此問題,該執行程序僅在必要時重新計划任務:
Executor inPlace = Runnable::run;
Thread forbidden = Thread.currentThread();
Executor forceBackground
= r -> (Thread.currentThread()==forbidden? poolExecutor: inPlace).execute(r);
…
future.whenCompleteAsync((r, e) -> taskMap.remove(key, future), forceBackground);
但是您可能會重新考慮是否真的需要這種復雜的按映射清理邏輯。 這不僅很復雜,而且可能會產生顯着的開銷,可能會安排許多清理操作,而這些清理操作在執行時已經過時了,它們並不是真正需要的。
執行起來可能會更簡單甚至更有效率
taskMap.values().removeIf(CompletableFuture::isDone);
不時清理整個地圖。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.