JavaのFutureプログラミングモードを簡単に説明します。

6073 ワード

Javaを使ってカバンを配ったことがある友達はFuture(interface)に詳しいかもしれませんが、Future自体は広く使われている同時設計のモデルです。データフローの同期が必要な同時応用開発を大幅に簡略化することができます。いくつかの分野の言語(Alice MLなど)では、文法的にもFutureを直接サポートしています。
ここではjava.util.co ncurrent.Futurtureを例にとって簡単にFutureの具体的な働き方を説明します。Futureオブジェクト自体は、非同期処理の結果に対する明示的な参照であると見なすことができる。その非同期性のために、作成当初は、参照されたオブジェクトはまだ利用可能ではないかもしれない(例えば、まだ演算中であるか、ネットワーク送信中であるか、待ち中であるかのような)。この場合、Futureを得るプログラムの流れがFutureで引用されているオブジェクトの使用を急がなければ、他のやりたいことができます。フローがFutureの背後に参照が必要なオブジェクトに進むと、2つのケースがあります。
このオブジェクトが利用可能であることを見て、後続のプロセスを完成させたいです。本当に利用できない場合は、他の分岐フローにも入ることができます。
あなたがいないと私の人生は意味を失います。だからたとえ海が荒れ果てても、あなたを待ちます。もちろん、根性なしに枯れてしまったら、タイムアウトを設けるのも理解できます。
前の場合は、Future.isDune()を呼び出して、参照の対象が準備されているかどうかを判断し、異なる処理を行うことができる。そして一つの場合はget()または
get(long timeout,TimeUnit unit)は、同期ブロッキングにより、オブジェクトの準備が完了するのを待つ。実際の運行期間がブロックされていますか?それともすぐに戻るかは、get()の呼び出しタイミングと対象準備ができている順です。
簡単に言えば、Futureモードは連続プロセスにおいてデータ駆動の同時需要を満たすことができ、同時に実行する性能向上を獲得しただけでなく、連続プロセスの簡潔で優雅さも失わない。
他の同時設計モードとの比較
Futureに加えて、他の比較的一般的な同時設計モードは、「コールバック駆動(マルチスレッド環境下)」「メッセージ/イベント駆動(Actorモデル中)」などを含む。
フィードバックは最も一般的な非同期モードで、即時性が高く、インターフェースデザインが簡単などがあります。しかし、Futureに対しては、その欠点も非常に明らかである。まず、マルチスレッド環境におけるコールバックは、通常、フィードバックをトリガするモジュールスレッドにおいて実行されるものであり、これは、コールバック方法を作成する際には、スレッド相互反発問題を考慮しなければならないことを意味する。第二に、コールバック方式インターフェースの提供者がこのモジュールのスレッドでユーザーアプリケーションのフィードバックを実行するのも比較的安全ではないので、どれぐらいの時間がかかりますか?あるいは何か異常があるかを確認できないため、このモジュールの即時性と信頼性が間接的に影響を受ける可能性があります。なお、コールバックインターフェースを使用すると、シーケンスフローの開発に不利です。コールバック方法の実行は孤立していますので、通常の流れと合流するのは難しいです。したがって、コールバックインターフェースは、コールバックで簡単なタスクだけを完了する必要があり、他のフローと合流する必要がないシーンに適しています。
これらのコールバックモードの欠点はまさにFutureの長項です。Futureの使用は非同期のデータを天然の融解手順に駆動するため、スレッド相互反発の問題を全く考慮する必要はなく、Futureは単一スレッドのプログラムモデル(例えば協働)においても実現できる(以下に述べる「Lazy Future」を参照)。一方、Futureインターフェースを提供するモジュールは、リカバリーインターフェースのような信頼性の問題や、このモジュールに対する可能性のある即時性の影響を全く心配する必要はない。
もう一つの一般的な同時設計モードは「メッセージ駆動」であり、これは一般的にActorモデルで運用されている。サービス要求者はサービス提供者にメッセージを送信し、その後サービス処理結果に依存しないタスクを継続し、依存結果が必要となる前に現在のプロセスを終了し、状態を記録する。メッセージに応答した後、記録された状態に応じて後続のフローがトリガされる。このような状態マシンに基づく同時制御は、コールバックよりも継続的な順序プロセスに適しているが、開発者は、非同期サービスの呼び出しに従って連続プロセスを複数の状態によって区別されるサブフローに切断しなければならない。Futureモードを使うとこの問題を回避できます。非同期呼び出しのために連続する流れを壊す必要はありません。しかし、特に注意すべき点は、Future.get()方法は、スレッドの実行をブロックする可能性があるので、通常は通常のActorモデルに直接に溶け込むことができないことである。協働プロセスに基づくActorモデルはこの衝突をより良く解決することができる)
Futureの柔軟性は、同期と非同期の自由取捨選択にも反映されています。開発者は、プロセスの必要性に応じて、「Future.isDune()」を待つかどうかを自由に決めることができます。いつ「Future.get()」を待てばいいですか?例えば、データが準備されているかどうかによって、この空き領域を利用して他のタスクを完了するかどうかを決めることができます。これは「非同期分岐予測」機構を実現するのにとても便利です。
Futureの派生
上記の基礎形態以外にも、Futureには豊富な派生変化があります。ここでいくつかの一般的なものを挙げます。
Lazy Future
一般的なFutureとは異なり、Lazy Futureは、創建当初は積極的に引用の対象を準備することなく、対象を請求する時から該当する作業を開始します。したがって、Lazy Future自体は合併を実現するためではなく、不必要な演算リソースの節約を出発点として、効果的にはLamda/Cloosureと似ています。いくつかのAPIを設計する場合、情報のセットを返す必要がありますが、中にはいくつかの情報の計算がかなりのリソースを消費する可能性があります。しかし、これらの情報は必ずしもすべてに関心を持っているわけではないので、多くのリソースを必要とする対象をLazy Futureとして提供し、特定の情報を必要としない場合はリソースを節約することができる。
また、Lazy Futureは、リソースの早期取得やロックによる不必要な相互反発を回避するためにも使用することができる。
Promise
PromiseはFutureの特殊な分岐と見なすことができ、一般的なFutureは、サービスの呼び出し時に直ちに処理またはLazy Futureの値をトリガするときに処理をトリガするように、サービス利用者によって直接非同期処理フローをトリガする。しかし、Promiseは、それらの非同期の流れがサービス利用者によって直接トリガされないシナリオを明示的に表すために使用される。例えばFutureインターフェースのタイミング制御は、その非同期的な流れは、調整者によってではなく、システムクロックによってトリガされ、また、タオバオの分散型購読フレームワークによって提供されるFuture型購読インターフェースのように、データの利用可能性は予約者によって決定されるのではなく、リリース者がいつデータを配信するかまたは更新するかによって決まる。したがって、標準的なFutureに対して、Promiseインターフェースは一つのset()またはfulfill()インターフェースが一般的に多くなります。
多重化可能なFuture
従来のFutureは使い捨てで、つまり非同期の処理結果を得ると、Futureオブジェクト自体が意味を失います。しかし、特別な設計を経たFutureは多重化も可能であり、これは複数の変更が可能なデータにとって非常に有用である。例えば、上記のタオバオ分布型購読フレームが提供するFuture式インターフェースは、何度もwaitNext()メソッドを呼び出すことができます(Future.get()毎回呼出時にブロックされるかどうかは、前回呼び出し後にまたデータが発表されるかどうかによって決まります。更新されていない場合は、次のデータのリリースまでブロックされます。このような設計の利点は、インターフェースの使用者は、任意の適切なタイミングで、または直接的に単純に独立したスレッド内の無限ループ応答によってデータの変化を購読することができ、他のタイミングタスクも考慮して、複数のFutureを同時に待つことができることである。簡略化された例は以下の通りである。

for (;;) {
 schedule = getNextScheduledTaskTime();
 while(schedule > now()) {
  try {
   data = subscription.waitNext(schedule - now());
   processData(data);
  } catch(Exception e) {...}
 }
 doScheduledTask();
}

Futureの使用
まず、Thinking in Javaのコードの一部を列挙します。これは同時進行中のCallableが使用する例です。コードは次の通りです。

//: concurrency/CallableDemo.java
import java.util.concurrent.*;
import java.util.*;

class TaskWithResult implements Callable<String> {
 private int id;
 public TaskWithResult(int id) {
  this.id = id;
 }
 public String call() {
  return "result of TaskWithResult " + id;
 }
}

public class CallableDemo {
 public static void main(String[] args) {
  ExecutorService exec = Executors.newCachedThreadPool();
  ArrayList<Future<String>> results =
   new ArrayList<Future<String>>();
  for(int i = 0; i < 10; i++)
   results.add(exec.submit(new TaskWithResult(i)));
  for(Future<String> fs : results)
   try {
    // get() blocks until completion:
    System.out.println(fs.get());
   } catch(InterruptedException e) {
    System.out.println(e);
    return;
   } catch(ExecutionException e) {
    System.out.println(e);
   } finally {
    exec.shutdown();
   }
 }
} /* Output:
result of TaskWithResult 0
result of TaskWithResult 1
result of TaskWithResult 2
result of TaskWithResult 3
result of TaskWithResult 4
result of TaskWithResult 5
result of TaskWithResult 6
result of TaskWithResult 7
result of TaskWithResult 8
result of TaskWithResult 9
*///:~ 

Futureの使用過程を説明します。まずExectorServiceオブジェクトexecからsubmit()を呼び出す方法はFutureオブジェクトを生成し、Callableで結果の特定のタイプを返してパラメータ化しました。isdone()メソッドを使ってFutureが完了したかどうかを調べることができます。タスクが完了すると、彼は結果を持っています。get()メソッドを呼び出して結果を得ることができます。isdone()で検査せずにget()を直接呼び出すこともできます。この場合、get()は結果が分かりにくくなります。タイムアウトしたget()関数を呼び出したり、先にジョブが完了したかどうかを確認してからget()を呼び出すことができます。