#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
под капотом, но это деталь реализации.