#java #multithreading #completable-future
#java #многопоточность #завершаемый-будущее
Вопрос:
Я бы ожидал, что следующий код всегда будет печатать «Ошибка», но иногда он печатает «внешний вход». Я хочу, чтобы «outer» завершил работу, а «inner» использовал результат «outer» для генерации своего собственного результата, который завершается неудачей, если в будущем произойдет сбой. Что я делаю не так?
CompletableFuture<String> outer = CompletableFuture.supplyAsync(() -> "outer");
CompletableFuture<String> inner = CompletableFuture.supplyAsync(() -> "inner");
inner.completeExceptionally(new IllegalArgumentException());
CompletableFuture<String> both = outer.thenApply(s -> {
try {
String i = inner.get();
return s i;
} catch (InterruptedException |ExecutionException e) {
throw new IllegalStateException(e);
}
});
try {
String o = both.get();
System.out.println(o);
} catch (ExecutionException | InterruptedException e) {
System.err.println("Errored");
}
Комментарии:
1.
inner
вообще не ссылаетсяouter
, так как же он может использоватьouter
результат?both
относится к ним обоим, что кажется разумным. Это то, что вы имели в виду?2. Но это так, он использует
outer
результат вthenAccept
блоке, нет?3. Это не in
inner
, это inboth
. Вот какinner
инициализируется:CompletableFuture<String> inner = CompletableFuture.supplyAsync(() -> "inner");
— это вообще не используетсяouter
.
Ответ №1:
На самом деле это довольно тривиально, чтобы понять это. Вам нужно более внимательно присмотреться к этому:
CompletableFuture<String> inner = CompletableFuture.supplyAsync(() -> "inner");
в частности, тот факт, что вы говорите supplyAsync
, что означает в другом потоке. В то время как в вашем основном потоке (или любом другом) вы делаете : inner.completeExceptionally(new IllegalArgumentException());
.
Какой поток должен выиграть? Тот, из supplyAsync
которого вы выполняете, или тот, который вы запускаете inner.completeExceptionally
? Конечно, в документации completeExceptionally
упоминается об этом… Это базовая «гонка», которая сначала достигает «завершения» (обычно или через исключение) inner
.
Комментарии:
1. Я думал, что вызов
inner.completeExceptionally
перед вызовомinner.get
гарантирует, что исключение будет сгенерировано.2. @Johnny a
Future
не является потоком, для его запуска не требуется операция «терминал» (get/join
может быть).CompletableFuture
Перед вызовом можно выполнитьget
, как подразумевается вget
документации: «Ожидает, если необходимо , завершения этого будущего …»
Ответ №2:
Ключ к пониманию того, что происходит, находится в javadoc for completeExceptionally
.
Если он еще не завершен, вызывает вызовы
get()
и связанные с ними методы для создания данного исключения.
В вашем примере вы создаете два фьючерса, которые будут завершены асинхронно, затем вы вызываете completeExceptionally
, чтобы сообщить одному из фьючерсов выдать исключение, а не выдавать результат.
Исходя из того, что вы говорите, кажется, что иногда inner
будущее уже завершено к моменту вашего вызова completeExceptionally
. В этом случае completeExceptionally
вызов не повлияет (согласно спецификации), а последующий inner.get()
выдаст результат, который был (уже) вычислен.
Это условие гонки.