Как использовать CompletableFuture.thenComposeAsync ()?

дано:

public class Test
{
    public static void main(String[] args)
    {
        int nThreads = 1;
        Executor e = Executors.newFixedThreadPool(nThreads);

        CompletableFuture.runAsync(() ->
        {
            System.out.println("Task 1. Thread: " + Thread.currentThread().getId());
        }, e).thenComposeAsync((Void unused) ->
        {
            return CompletableFuture.runAsync(() ->
            {
                System.out.println("Task 2. Thread: " + Thread.currentThread().getId());
            }, e);
        }, e).join();
        System.out.println("finished");
    }
}

Я ожидаю, что один поток исполнителя выполнит задачу 1, а затем задачу 2. Вместо этого код зависает, если nThreads меньше 2.

  1. Пожалуйста, объясните, почему код зависает. Я вижу, что он заблокирован в CompletableFuture: 616 ждут подарки!--2--> для завершения, но непонятно почему.
  2. если я разрешаю использовать 2 потока, для чего используется каждый поток?

короче говоря, Пожалуйста, помогите мне понять как thenComposeAsync() на самом деле работает. Javadoc выглядит так, как будто он был написан для роботов вместо людей:)

2 ответов


  1. на thenComposeAsync метод помещает новую задачу для вашего исполнителя, который захватывает один поток и ждет вашего Task 2 завершить. Но у этого больше нет ниток. Вместо этого вы можете использовать thenCompose метод, который выполняется в том же потоке, что и Task 1 чтобы избежать взаимоблокировок.

  2. выполняется один поток Task 1 и Task 2 и второй заботится о составлении результатов двух.

Примечание.: CompletableFuture(s) Лучше всего работать с ForkJoinPool это более эффективно при обработке задач, которые порождают новые задачи. Значение по умолчанию ForkJoinPool был добавлен в Java 8 для этой цели и используется по умолчанию, если не указать исполнитель для выполнения ваших задач.

вот хорошая презентация о том, где эти новые функции светит и как они работают:реактивные Шаблоны программирования С Java 8 Futures.


блокирует на runAsync что внутри thenComposeAsync. thenComposeAsync запускает поставляемую функцию в потоке внутри executor e. Но функция, которую вы ей дали, пытается выполнить тело runAsync внутри того же исполнителя.

вы можете лучше видеть, что происходит, добавив другой вывод трассировки:

CompletableFuture.runAsync(() -> {
    System.out.println("Task 1. Thread: " + Thread.currentThread().getId());
}, e).thenComposeAsync((Void unused) -> {
    System.out.println("Task 1 1/2. Thread: " + Thread.currentThread().getId());
    return CompletableFuture.runAsync(() -> {
        System.out.println("Task 2. Thread: " + Thread.currentThread().getId());
    }, e);
}, e).join();

теперь, если вы запустите его с помощью 2-thread executor, вы увидите, что задача 1 1/2 и Задача 2 выполняются в разных потоках.

способ исправить это-заменить thenComposeAsync с обычными thenCompose. Я не уверен, почему вы могли бы использовать thenComposeAsync. Если у вас есть метод, который возвращает CompletableFuture, предположительно, этот метод не блокируется и не должен выполняться асинхронно.