Порядок завершения CompletableFuture

#java #concurrency #completable-future

#java #параллелизм #завершаемый-будущее

Вопрос:

Мне любопытно понять порядок завершения, когда CompletableFuture имеет несколько иждивенцев. Я бы ожидал, что иждивенцы будут завершены в том порядке, в котором они были добавлены, но, похоже, это не так.

В частности, такое поведение вызывает удивление:

 import java.util.concurrent.CompletableFuture;

public class Main {

    public static void main(String[] args) {
        {
            var x = new CompletableFuture<Void>();
            x.thenRun(() -> System.out.println(1));
            x.thenRun(() -> System.out.println(2));
            x.thenRun(() -> System.out.println(3));
            x.complete(null);
        }
        {
            var x = new CompletableFuture<Void>();
            var y = x.copy();
            y.thenRun(() -> System.out.println(1));
            y.thenRun(() -> System.out.println(2));
            y.thenRun(() -> System.out.println(3));
            x.complete(null);
        }
    }
}
 

…приводит к следующему выводу…

 3
2
1
1
2
3
 

Комментарии:

1. Первая строка документа внутренней реализации, A CompletableFuture may have dependent completion actions, collected in a linked stack. хотя и не входит в общедоступную спецификацию, выполняет зависимости в порядке LIFO.

Ответ №1:

Все 3 дочерних фьючерса, сгенерированных выражением ‘x.thenRun ()’, выполняются параллельно, поэтому вы не можете ожидать какого-либо определенного порядка напечатанных чисел.

То же самое для ‘y.thenRun()’.

Выраженный порядок является особенностью реализации, может измениться в будущем и не поддерживается спецификацией.

Комментарии:

1. Выражения не выполняются параллельно. Здесь не происходит асинхронной отправки.

Ответ №2:

Итак, вы не связали их в цепочку thenRun , поэтому в заказе нет нулевых гарантий, но вы ожидаете какого-то порядка? Извините, это не так, как это работает.

В документации ничего не говорится о порядке выполнения в таких случаях, поэтому все, что вы видите сейчас на выходе:

  • не является гарантией
  • может меняться от запуска к запуску (и от версии java к версии)

Это немного сложно доказать с помощью кода с вашей текущей настройкой, но просто измените его на :

 var x = CompletableFuture.runAsync(() -> {
            LockSupport.parkNanos(TimeUnit.SECONDS.toNanos(1));
        });
x.thenRun(() -> System.out.println(1));
x.thenRun(() -> System.out.println(2));
x.thenRun(() -> System.out.println(3));
x.join();
 

И запустите это, скажем 20 times , я уверен, что хотя бы один раз вы не увидите 3, 2, 1 , но что-то другое.

Комментарии:

1. Порядок выполнения асинхронных вызовов здесь не имеет значения, я имею в виду только порядок синхронных вызовов.

2. @Vaci но в спецификации не говорится ни о каком порядке, поэтому вы не можете его предполагать. Это stack под капотом, но это деталь реализации.