CompletableFuture.get 没有等待最后一个区块

CompletableFuture.get not waiting for final block

我有一个自定义的 ExecutorService,

class CustomExecutorService implements ExecutorService {

        ExecutorService delegate;

        public CustomExecutorService(ExecutorService delegate){
            this.delegate = delegate;
        }
        // All methods are just delegated to the delegate except execute
        
        public void execute(Runnable command) {
            this.delegate.execute(wrapRunnable(command));
        }

        private Runnable wrapRunnable(final Runnable command) {
            return () -> {
                try {
                    command.run();
                } finally {
                    try {
                        Thread.sleep(2000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("Finalizer done.");
                }
            };
        }
    }

现在这是我的测试代码,

ExecutorService executorService = new CustomExecutorService(Executors.newCachedThreadPool());
CompletableFuture.runAsync(() -> System.out.println("Command done."), executorService).get();
System.out.println("Main Thread Done.");

现在我希望发生以下输出,

Command done.
Finalizer done. //After a wait of 2 seconds
Main Thread Done.

但是发生的事情是,

Command done.
Main Thread Done.

它甚至没有等待 finally 块并且主线程正在退出。我无法解释这种行为,因为 CompletableFuture#get 应该是阻塞的,但它似乎不是在等待 finallly 阻塞。

我知道如果 finally 将由守护线程执行并且所有其他非守护线程在 finally 之前退出,则不能保证执行 finally叫做。但是 ExecutorService 线程是非守护线程,所以 finally 应该被执行。

我是 运行 这个 corretto-11.0.11

查看CompletableFuture的源代码第1627行(Jdk 1.8)如下所示:

public void run() {
    CompletableFuture<Void> d; Runnable f;
        if ((d = dep) != null && (f = fn) != null) {
            dep = null; fn = null;
            if (d.result == null) {
                try {
                    f.run();
                    d.completeNull(); // line 1627
                } catch (Throwable ex) {
                    d.completeThrowable(ex);
                }
            }
            d.postComplete();
        }
    }
}

Runnable f 完成后,CompletableFuture 的结果将设置为 null,然后 get() 方法将不再被阻塞。在你的情况下 Runnable f 等于 System.out.println("Command done.")

你可以在CompletableFuture runAsync(Runnable runnable, Executor executor)的入口下个断点,一步步调试,你会发现什么时候设置了CompletableFuture的结果。

当您 运行 CompletableFuture.runAsync 时,它会将您的 Runnable 包装在一个任务中(来源中的 AsyncRun),运行 是 运行nable,然后将 CompletableFuture 标记为完成。这是传递给执行者的内容。

在你的执行器中,你正在包装 那个 任务,所以当你的 finally 块 运行 时,未来已经标记为完成,这就是为什么get() returns 紧随其后。