簡體   English   中英

如何防止在上下文線程中執行CompletableFuture#whenComplete

[英]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;
    });

此代碼的問題是futurewhenComplete函數參數與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.

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