Вызов метода для блоков Future.get(). Это действительно желательно?

Пожалуйста, внимательно прочитайте вопрос, прежде чем пометить его как дубликат.

Ниже приведен фрагмент псевдокода. Мой вопрос: не нарушает ли приведенный ниже код само понятие параллельной асинхронной обработки?

Причина, по которой я спрашиваю об этом, заключается в том, что в приведенном ниже коде основной поток отправит задачу для выполнения в другом потоке. После отправки задачи в очередь она блокирует метод Future.get(), чтобы задача возвращала значение. Я предпочел бы, чтобы задача выполнялась в главном потоке, а не отправлялась в другой поток и ожидала результатов. Что я получил, выполнив задачу в новом потоке?

Я знаю, что вы могли бы подождать ограниченное время и т. Д., Но что, если я действительно забочусь о результате? Проблема усугубляется, если нужно выполнить несколько задач. Мне кажется, что мы просто делаем работу синхронно. Мне известна библиотека Guava, которая обеспечивает неблокирующий интерфейс слушателя. Но мне интересно знать, правильно ли я понимаю API Future.get(). Если это правильно, почему Future.get() предназначен для блокировки, тем самым побеждая весь процесс параллельной обработки?

Примечание - для записи я использую JAVA 6

public static void main(String[] args){

private ExectorService executorService = ...

Future future = executorService.submit(new Callable(){
    public Object call() throws Exception {
        System.out.println("Asynchronous Callable");
        return "Callable Result";
    }
});

System.out.println("future.get() = " + future.get());
}

5 ответов

Решение

Future предлагает вам метод isDone() который не блокирует и возвращает true, если вычисление завершено, иначе false.

Future.get() используется для получения результата вычисления.

У вас есть несколько вариантов:

  • вызов isDone() и если результат готов, попросите его, вызвав get()обратите внимание, как нет блокировки
  • блокировать на неопределенный срок get()
  • блок для указанного тайм-аута с get(long timeout, TimeUnit unit)

Целый Future API дело в том, чтобы иметь простой способ получения значений из потоков, выполняющих параллельные задачи. Это может быть сделано синхронно или асинхронно, если вы предпочитаете, как описано в пулях выше.

ОБНОВЛЕНИЕ С ПРИМЕРОМ CACHE

Вот реализация кэша от Java Concurrency In Practice, отличный пример использования для Future,

  • Если вычисление уже выполняется, вызывающая сторона, заинтересованная в результате вычисления, будет ждать окончания вычислений
  • Если результат будет готов в кеше, вызывающий будет его собирать
  • если результат не готов, а вычисление еще не началось, вызывающая сторона начнет вычисление и обернет результат в Future для других абонентов.

Это все легко достигается с Future API.

package net.jcip.examples;

import java.util.concurrent.*;
/**
 * Memoizer
 * <p/>
 * Final implementation of Memoizer
 *
 * @author Brian Goetz and Tim Peierls
 */
public class Memoizer <A, V> implements Computable<A, V> {
    private final ConcurrentMap<A, Future<V>> cache
            = new ConcurrentHashMap<A, Future<V>>();
    private final Computable<A, V> c;

public Memoizer(Computable<A, V> c) {
    this.c = c;
}

public V compute(final A arg) throws InterruptedException {
    while (true) {

        Future<V> f = cache.get(arg);
        // computation not started
        if (f == null) {
            Callable<V> eval = new Callable<V>() {
                public V call() throws InterruptedException {
                    return c.compute(arg);
                }
            };

            FutureTask<V> ft = new FutureTask<V>(eval);
            f = cache.putIfAbsent(arg, ft);
            // start computation if it's not started in the meantime
            if (f == null) {
                f = ft;
                ft.run();
            }
        }

        // get result if ready, otherwise block and wait
        try {
            return f.get();
        } catch (CancellationException e) {
            cache.remove(arg, f);
        } catch (ExecutionException e) {
            throw LaunderThrowable.launderThrowable(e.getCause());
        }
    }
  }
}

Ниже приведен фрагмент псевдокода. Мой вопрос: не нарушает ли приведенный ниже код само понятие параллельной асинхронной обработки?

Все зависит от вашего варианта использования:

  1. Если вы действительно хотите заблокировать, пока не получите результат, используйте блокировку get()
  2. Если вы можете ждать определенного периода, чтобы узнать статус, а не бесконечную продолжительность блокировки, используйте get() с тайм-аутом
  3. Если вы можете продолжить, не анализируя результат сразу и проверить результат в будущем, используйте CompletableFuture (Java 8)

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

  4. Вы можете реализовать механизм обратного вызова из вашего Runnable/Callable. Посмотрите на вопрос SE ниже:

    Исполнители Java: как получить уведомление, не блокируя, когда задача завершена?

Я хотел бы поделиться своим мнением по этому вопросу, более теоретически, поскольку уже есть некоторые технические ответы. Я хотел бы основать свой ответ на комментарии:

Позвольте мне привести вам мой пример. Задачи, которые я отправляю службе, в конечном итоге вызывают запросы HTTP. Результат запроса HTTP может занять много времени. Но мне нужен результат каждого HTTP-запроса. Задачи представлены в цикле. Если я жду, когда каждая задача вернется (получится), то я теряю здесь параллелизм, не так ли?

что согласуется с тем, что сказано в вопросе.

Скажем, у вас трое детей, и вы хотите сделать торт на свой день рождения. Так как вы хотите приготовить самый большой торт, вам понадобится много разных вещей для его приготовления. Итак, что вы делаете, это делите ингредиенты на три разных списка, потому что там, где вы живете, есть только 3 супермаркета, которые продают разные продукты, и назначают каждому из ваших детей одну задачу, simultaneously,

Теперь, прежде чем вы сможете начать готовить торт (давайте снова предположим, что вам нужны все ингредиенты заранее), вам нужно будет дождаться ребенка, который должен пройти самый длинный маршрут. Теперь тот факт, что вам нужно подождать всех ингредиентов, прежде чем начинать готовить торт, - это ваша необходимость, а не зависимость между задачами. Ваши дети работали над заданиями одновременно так долго, как могли (например, пока первый ребенок не выполнил задание). Итак, в заключение, у вас есть паралеллизм.

Последовательный пример описан, когда у вас есть 1 ребенок, и вы назначаете ему все три задачи.

В приведенном вами примере вы можете запустить все в своем main() метод и иди своей веселой дорогой.

Но давайте предположим, что у вас есть три шага вычислений, которые вы выполняете в настоящее время последовательно. Просто для понимания, давайте предположим, что этап 1 занимает t1 секунду, шаг 2 - t2 секунды, а шаг 3 - t3 секунды. Таким образом, общее время вычислений t1+t2+t3, Кроме того, давайте предположим, что t2>t1>=t3,

Теперь давайте рассмотрим сценарий, когда мы выполнили эти три шага параллельно, используя Future держать каждый вычислительный результат. Вы можете проверить, выполняется ли каждая задача, используя неблокирование isDone() позвонить по соответствующим фьючерсам. Что теперь происходит? теоретически ваше исполнение так же быстро, как t2 завершает правильно? Таким образом, мы получили некоторые преимущества от параллелизма.

Кроме того, в Java8 есть CompletableFuture который поддерживает функциональные обратные вызовы стиля.

Если вас не интересуют результаты, создайте новый поток и используйте этот поток. ExectorService API для подачи задач. Таким образом, ваш родительский поток, т.е. main Поток не будет блокировать каким-либо образом, он просто создаст новый поток, а затем начнет дальнейшее выполнение, пока новый поток отправит ваши задачи.

Для создания новой темы - либо сделать это самостоятельно, имея ThreadFactory для создания асинхронного потока или использовать некоторую реализацию java.util.concurrent.Executor,

Если это в приложении JEE и вы используете Spring Framework, то вы можете легко создать новый асинхронный поток, используя @async аннотаций.

Надеюсь это поможет!

Другие вопросы по тегам