web-dev-qa-db-ja.com

Future.get()ブロックのメソッド呼び出し。それは本当に望ましいですか?

これを重複としてマークする前に、質問を注意深く読んでください。

以下は、擬似コードのスニペットです。私の質問は次のとおりです-以下のコードは並列非同期処理の概念を無効にしませんか?

これを尋ねる理由は、以下のコードでは、メインスレッドが別のスレッドで実行されるタスクを送信するためです。キューでタスクを送信した後、Future.get()メソッドでブロックして、タスクが値を返すようにします。別のスレッドにサブミットして結果を待つのではなく、メインスレッドでタスクを実行したいです。新しいスレッドでタスクを実行することで得たものは何ですか?

あなたは限られた時間などを待つことができることを知っていますが、結果を本当に気にしたらどうなりますか?実行するタスクが複数ある場合、問題は悪化します。私はちょうど同期的に作業をしているように思えます。私は、非ブロッキングリスナインターフェイスを提供するGuavaライブラリを知っています。しかし、Future.get()APIに対する私の理解が正しいかどうかを知りたいと思っています。正しい場合、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());
}
52

Futureは、ブロックしないメソッドisDone()を提供し、計算が完了するとtrueを返し、そうでない場合はfalseを返します。

Future.get()は、計算結果を取得するために使用されます。

いくつかのオプションがあります:

  • isDone()を呼び出し、結果の準備ができている場合は、get()を呼び出して結果を要求し、ブロッキングがないことを確認します。
  • get()で無期限にブロックする
  • get(long timeout, TimeUnit unit)で指定されたタイムアウトのブロック

Future API全体は、並列タスクを実行するスレッドから値を簡単に取得するためのものです。これは、上記の箇条書きで説明したように、必要に応じて同期または非同期で実行できます。

キャッシュの例で更新

以下は、Java Concurrency In Practiceのキャッシュ実装です。Futureの優れた使用例です。

  • 計算が既に実行されている場合、計算の結果に関心のある呼び出し元は、計算が完了するまで待機します
  • 結果がキャッシュに用意されている場合、呼び出し元はそれを収集します
  • 結果の準備ができておらず、計算がまだ開始されていない場合、呼び出し元は計算を開始し、他の呼び出し元の結果をFutureにラップします。

これはすべて、Future AP​​Iを使用して簡単に実現できます。

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());
        }
    }
  }
}
43
John

以下は、擬似コードのスニペットです。私の質問は、次のコードは並列非同期処理の概念を打ち負かしていないのですか?

それはすべてユースケースに依存します:

  1. 結果が得られるまで本当にブロックしたい場合は、ブロックget()を使用します
  2. 無限のブロック期間ではなく、特定の期間待機してステータスを知ることができる場合は、タイムアウトでget()を使用します
  3. 結果をすぐに分析せずに続行し、将来的に結果を検査できる場合は、 CompletableFuture (Java 8)を使用します

    明示的に完了(値とステータスを設定)できるFutureであり、CompletionStageとして使用でき、完了時にトリガーされる依存機能とアクションをサポートします。

  4. Runnable/Callableからコールバックメカニズムを実装できます。以下のSEの質問をご覧ください。

    Javaエグゼキューター:タスクの完了時にブロックせずに通知する方法

8
Ravindra babu

すでにいくつかの技術的な答えがありますので、これについて、理論的な観点から、私のシェアをお伝えしたいと思います。私はコメントに基づいて答えをしたいと思います:

私の例を挙げましょう。サービスに送信するタスクは、HTTPリクエストを発生させることになります。HTTPリクエストの結果には多くの時間がかかります。ただし、各HTTPリクエストの結果が必要です。タスクはループで送信されます。各タスクが戻る(取得する)のを待つと、ここで並列処理が失われますよね?

それは質問で言われたことに同意します。

3人の子供がいて、誕生日にケーキを作りたいとします。最高のケーキを作りたいので、それを準備するために多くの異なるものが必要です。あなたが行うことは、3つの異なるリストに材料を分割することです。なぜなら、あなたが住んでいる場所には、異なる製品を販売する3つのスーパーマーケットがあり、各子供に単一のタスクsimultaneouslyを割り当てるからです。

さて、ケーキの準備を始める前に(もう一度考えてみましょう、事前にすべての材料が必要だとしましょう)、最も長いルートを踏まなければならない子供を待つ必要があります。さて、ケーキを作る前にすべての材料を待つ必要があるという事実は、タスク間の依存関係ではなく、your必要性です。あなたの子供たちは、できる限り同時にタスクに取り組んでいます(たとえば、最初の子供がタスクを完了するまで)。結論として、ここにパラレルレルムがあります。

1人の子供がいて、3つのタスクすべてを彼/彼女に割り当てる場合の連続的な例について説明します。

1
NiVeR

結果を気にしない場合は、新しいスレッドを生成し、そのスレッドからタスク送信にExectorService AP​​Iを使用します。その方法では、親スレッド、つまりmainスレッドはブロックされず、単に新しいスレッドを生成してから実行を開始しますが、新しいスレッドはタスクを送信します。

新しいスレッドを作成するには-非同期スレッドの作成にThreadFactoryを使用して自分で行うか、Java.util.concurrent.Executorの実装を使用します。

これがJEEアプリケーションにあり、Springフレームワークを使用している場合、@asyncアノテーションを使用して新しい非同期スレッドを簡単に作成できます。

お役に立てれば!

1
hagrawal

あなたが与えた例では、main()メソッドですべてを実行し、あなたの陽気な道を行くかもしれません。

ただし、現在連続して実行している3つの計算ステップがあると仮定しましょう。理解のためだけに、step1がt1秒、step2がt2秒、step3がt3秒かかると仮定します。したがって、合計計算時間はt1+t2+t3です。また、t2>t1>=t3と仮定します。

Futureを使用してこれらの3つのステップを並列に実行し、各計算結果を保持するシナリオを考えてみましょう。対応する先物でノンブロッキングisDone()呼び出しを使用して、各タスクが完了したかどうかを確認できます。さてどうなりますか?理論的には、実行はt2が正しく完了するのと同じくらい速いですか? did並列処理からいくつかの利点を得ることができます。

また、Java8には、機能スタイルのコールバックをサポートするCompletableFutureがあります。

1
ring bearer