На сколько плохо использовать join внутри Future?

Рейтинг: 2Ответов: 1Опубликовано: 15.02.2023

Есть ли принципиальная разница между этими примерами кода?

someCompletionStage.thenApply(string -> doSomething(string));
CompletableFuture.supplyAsync(() -> {
    String string = someCompletionStage.join();
    doSomething(string);
});

Понимаю, что первое идиоматичнее и легче читается, но есть ли разница в поведении и эффективности?

Ответы

▲ 1

join внутри Future нужно использовать очень аккуратно, вот пример дэдлока

import java.util.concurrent.*;

public class Program
{
    public static void main(String[] args) throws Exception {
        var p = Executors.newSingleThreadExecutor();
        var someCompletionStage = CompletableFuture
           .supplyAsync(()->{
                try { Thread.sleep(1000); } catch (Exception ex) {}
                return "hi!";
           }, p)
           .thenApplyAsync(s -> {
               try { Thread.sleep(1000); } catch (Exception ex) {}
               return s;
           }, p);
        CompletableFuture.runAsync(() -> {
            String string = someCompletionStage.join();
            System.out.println(string);
        }, p)
         .join();
        System.out.println("bye!");
        p.shutdown();
    }
}

Происходит это потому, что "третий" CompletableFuture.runAsync успевает вклиниться между "первым" supplyAsync и "вторым" thenApplyAsync, блокирует поток джойном, поэтому вторая часть уже никогда не выполнится.

С цепочкой вызовов, естественно, дэдлок не случится.