Java CompletableFuture.allOf().orTimeout()的意外行为

Java CompletableFuture.allOf().orTimeout()的意外行为,java,asynchronous,timeout,future,Java,Asynchronous,Timeout,Future,有两种方法可以在给定的时间量后强制超时: 我希望他们也会这样做。但是,当应用于时,这两种提供超时的方式的行为完全不同 基本上,get() 下面是一些代码来演示我观察到的行为: import org.junit.Test; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeoutExce

有两种方法可以在给定的时间量后强制超时:

我希望他们也会这样做。但是,当应用于时,这两种提供超时的方式的行为完全不同

基本上,
get()

下面是一些代码来演示我观察到的行为:

import org.junit.Test;

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeoutException;

import static java.util.concurrent.TimeUnit.MILLISECONDS;

public class AllOfWithTimeoutTest {

    public static final int TIMEOUT_IN_MILLIS = 100;

    @Test
    public void allOfOrTimeout1() throws InterruptedException, ExecutionException, TimeoutException {
        getAllOfFuture().get(TIMEOUT_IN_MILLIS, MILLISECONDS);
    }

    @Test
    public void allOfOrTimeout2() throws ExecutionException, InterruptedException {
        getAllOfFuture().orTimeout(TIMEOUT_IN_MILLIS, MILLISECONDS);
    }

    private CompletableFuture<Void> getAllOfFuture() {
        return CompletableFuture.allOf(
            CompletableFuture.runAsync(() -> sleep(1)),
            CompletableFuture.runAsync(() -> sleep(2)),
            CompletableFuture.runAsync(() -> sleep(3)),
            CompletableFuture.runAsync(() -> sleep(4)),
            CompletableFuture.runAsync(() -> sleep(5)),
            CompletableFuture.runAsync(() -> sleep(6)),
            CompletableFuture.runAsync(() -> sleep(7)),
            CompletableFuture.runAsync(() -> sleep(8))
        );
    }

    public static void sleep(int millis) {
        try {
            Thread.sleep(millis);
            System.out.format("Had a nap for %s milliseconds.\r\n", millis);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    };
}
allOfOrTimeout2()
的打印输出不是我所期望的,每次执行时都略有不同。它通常在前2到5行之间打印,但从不在前8行之间打印。这是一个只打印了两行的版本:

Had a nap for 1 milliseconds.
Had a nap for 2 milliseconds.
另外,如果我在IntelliJ中运行整个测试,我会在最后得到一些额外的行:

Had a nap for 1 milliseconds.
Had a nap for 2 milliseconds.
Had a nap for 3 milliseconds.
Had a nap for 4 milliseconds.
Had a nap for 5 milliseconds.
Had a nap for 6 milliseconds.
Had a nap for 7 milliseconds.
Had a nap for 8 milliseconds.



Had a nap for 1 milliseconds.
Had a nap for 2 milliseconds.

Had a nap for 3 milliseconds.
Had a nap for 4 milliseconds.

Process finished with exit code 0
Had a nap for 
我的问题是:

  • 这是
    orTimeout()
    的预期行为吗
  • 如果没有,为什么要这样做
  • 这是orTimeout()的预期行为吗


    OrTimeout
    是非阻塞异步调用,而
    get
    是阻塞异步调用。

    是,这是预期行为

    我认为首先要认识到的关键是,
    CompletableFuture
    中的
    async
    方法默认情况下会在上运行任务,其中所有线程都设置为,这意味着JVM不会等到完成后才退出。的Javadocs中有一个注释提到了这一点:“所有没有显式执行器参数的异步方法都是使用ForkJoinPool.commonPool()执行的。”

    记住这一点,
    orTimeout()
    使当前的
    CompletableFuture
    (这是所有8个
    CompletableFuture
    任务的组合)完成,但如果在给定的超时时间内未正常完成,则会出现异常。与
    get()
    不同,这不会阻止当前线程,而是简单地为
    getAllOfFuture()
    返回的
    CompletableFuture
    添加超时。调用
    orTimeout()
    后,当前线程将继续,因为没有什么事情可做,JVM在所有任务都有机会运行之前完成,因为它们是由守护进程线程执行的


    至于为什么只有少数任务需要完成,我怀疑这是因为JUnit测试后处理阶段,即在测试方法完成后,主线程仍然必须运行一些JUnit后处理代码,这给了一些工作线程完成任务的时间。如果在正常的main方法(JUnit之外)中运行代码,那么
    orTimeout
    示例可能甚至不会有单个任务输出,因为线程会立即完成。

    我想如果您这样编写:

    public static void allOfOrTimeout2() {
        CompletableFuture<Void> future1 = getAllOfFutures();
        CompletableFuture<Void> future2 = future1.orTimeout(TIMEOUT_IN_MILLIS, MILLISECONDS);
    }
    
    它需要调用
    orTimeout
    ,文档说明:

    如果未在给定超时之前完成,则例外情况下使用TimeoutException完成此CompletableFuture

    future1
    完成了吗?这又是一个时间问题,但很可能不是(要完成它,所有那些
    runAsync
    需要完成)。因此没有超时,调用
    alloftimeout2
    就结束了

    现在运行
    runAsync
    中代码的线程称为守护线程,VM不必等待它们结束后才能关闭。因此,
    main
    线程结束了,这些线程没有停止VM,所以一切都结束了

    由于整个输出取决于线程的调度方式,因此无法预测它。您甚至无法预测未来2是否在VM关闭之前完成



    当然,如果您想要可预测的结果:您可以等待
    future2
    完成,或者将执行器传递给
    runAsync

    ,您可以在方法
    alloftimeout2
    结束之前添加
    Thread.sleep()?谢谢你有这个问题的答案吗?
    
    public static void allOfOrTimeout2() {
        CompletableFuture<Void> future1 = getAllOfFutures();
        CompletableFuture<Void> future2 = future1.orTimeout(TIMEOUT_IN_MILLIS, MILLISECONDS);
    }
    
    CompletableFuture<Void> future2 = future1.orTimeout(TIMEOUT_IN_MILLIS, MILLISECONDS);