Подтвердить что ты не робот

ЗавершенныеFuture, supplyAsync() и thenApply()

Нужно что-то подтвердить. Следующий код:

CompletableFuture
    .supplyAsync(() -> {return doSomethingAndReturnA();})
    .thenApply(a -> convertToB(a));

будет таким же, как:

CompletableFuture
    .supplyAsync(() -> {
        A a = doSomethingAndReturnA();
        convertToB(a);
 });

Правильно? Есть ли причина, по которой мы бы использовали thenApply, кроме того: 1) с большим кодом для преобразования или 2) нужно повторно использовать лямбда-блок в других местах?

4b9b3361

Ответ 1

Это не одно и то же. Во втором примере, когда thenApply не используется, уверен, что вызов convertToB выполняется в том же потоке, что и метод doSomethingAndReturnA.

Но в первом примере, когда используется метод thenApply, могут случиться другие вещи.

Прежде всего, если завершен CompletableFuture, выполняющий doSomethingAndReturnA, вызов thenApply произойдет в потоке вызывающего. Если CompletableFutures не был завершен, Function, переданный в thenApply, будет вызываться в том же потоке, что и doSomethingAndReturnA.

Непонятный? Хорошо эта статья может быть полезна (спасибо @SotiriosDelimanolis за ссылку).

Я привел короткий пример, иллюстрирующий, как работает thenApply.

public class CompletableTest {
    public static void main(String... args) throws ExecutionException, InterruptedException {
        final CompletableFuture<Integer> future = CompletableFuture
                .supplyAsync(() -> doSomethingAndReturnA())
                .thenApply(a -> convertToB(a));

        future.get();
    }

    private static int convertToB(final String a) {
        System.out.println("convertToB: " + Thread.currentThread().getName());
        return Integer.parseInt(a);
    }

    private static String doSomethingAndReturnA() {
        System.out.println("doSomethingAndReturnA: " + Thread.currentThread().getName());
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        return "1";
    }
}

И результат:

doSomethingAndReturnA: ForkJoinPool.commonPool-worker-1
convertToB: ForkJoinPool.commonPool-worker-1

Итак, когда первая операция медленная (т.е. CompletableFuture еще не завершена), оба вызова происходят в одном потоке. Но если мы должны были удалить Thread.sleep -call из doSomethingAndReturnA, то вывод (может быть) следующий:

doSomethingAndReturnA: ForkJoinPool.commonPool-worker-1
convertToB: main

Обратите внимание, что вызов convertToB находится в потоке main.

Ответ 2

thenApply() - это функция обратного вызова, которая будет выполняться, когда supplyAsync() возвращает значение.

В фрагменте кода 2 поток, который вызывает doSomethingAndReturnA(), ожидает, что функция будет выполнена и вернет данные. Но в некоторых исключительных случаях (например, при вызове webservice и ожидании ответа) поток должен ждать более длительную продолжительность ответа, что, в свою очередь, потребляет много системных ресурсов (только для ожидания ответа).

Чтобы избежать этого, вполне возможно будущее с функцией обратного вызова, где после вызова doSomethingAndReturnA() отдельный поток позаботится о выполнении doSomethingAndReturnA(), и основной поток вызывающего будет продолжать выполнять другую операцию, не дожидаясь ответа для возврата. Как только будет получен ответ doSomethingAndReturnA, будет вызван метод обратного вызова (т.е. ThenApply())