1概要

この記事では、


Future


について学びます。

Java 1.5から登場したインターフェースで、非同期呼び出しや並行処理を扱うときに非常に便利です。


2

未来

を作成する

簡単に言うと、

Future

クラスは非同期計算の将来の結果、つまり処理が完了した後に最終的に

Future

に現れる結果を表します。


Future

インスタンスを作成して返すメソッドの書き方を見てみましょう。

実行時間の長いメソッドは、非同期処理と

Future

インタフェースに適しています。これにより、

Future

にカプセル化されたタスクが完了するのを待っている間に他のプロセスを実行することができます。


Future

の非同期性を活用する操作の例をいくつか示します。

  • 計算集約的プロセス(数学的および科学的)

計算)
** 大きなデータ構造を操作する(ビッグデータ)

  • リモートメソッド呼び出し(ファイルのダウンロード、HTMLの廃棄、Webサービス)。


2.1.

FutureTask


による

Futures

の実装

この例では、

Integer

の2乗を計算する非常に単純なクラスを作成します。これは間違いなく「長時間実行」メソッドのカテゴリには当てはまりませんが、完了までに1秒かかるように

Thread.sleep()

呼び出しを追加します。

public class SquareCalculator {

    private ExecutorService executor
      = Executors.newSingleThreadExecutor();

    public Future<Integer> calculate(Integer input) {
        return executor.submit(() -> {
            Thread.sleep(1000);
            return input **  input;
        });
    }
}

実際に計算を実行するコードの一部は、

call()

メソッド内に含まれており、ラムダ式として提供されます。ご覧のとおり、それに関して特別なことは何もありませんが、前述の

sleep()

呼び出しを除きます。



Callable





https://docsの使用方法に注意を向けると、より興味深いものになります。

.oracle.com/javase/8/docs/api/java/util/concurrent/ExecutorService.html[ExecutorService]


Callable

は、結果を返すタスクを表すインタフェースで、単一の

call()

メソッドを持ちます。ここでは、ラムダ式を使用してそのインスタンスを作成しました。


Callable

のインスタンスを作成しても、どこにも移動することはできません。このタスクを新しいスレッドで開始して貴重な

Future

オブジェクトを返すエグゼキュータに渡す必要があります。それが

ExecutorService

の出番です。


ExecutorService

インスタンスを取得するには、いくつかの方法があります。それらのほとんどは、ユーティリティクラス

httpsで提供されています。エグゼキュータ]静的ファクトリメソッド。この例では、基本的な

newSingleThreadExecutor()

を使用しました。これにより、一度に1つのスレッドを処理できる

ExecutorService__が可能になります。


ExecutorService

オブジェクトを作成したら、引数として

Callable

を渡して

submit()を呼び出すだけです。

submit()

はタスクを開始し、


https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/FutureTask.html


オブジェクトを返します

Future__インターフェースの実装


3

未来


を消費する

ここまでで、

Future

のインスタンスを作成する方法を学びました。

このセクションでは、このインスタンスを扱う方法を学びます

このセクションでは、

Future

のAPIの一部であるすべてのメソッドを調べることによって、このインスタンスを操作する方法を学習します。


3.1. 結果を取得するための

isDone()

および

get()

の使用

今度は

calculate()

を呼び出し、返された

Future

を使用して結果の

Integer

を取得する必要があります。

Future

APIからの2つのメソッドは、このタスクに役立ちます。



Future.isDone()


は、executorがタスクの処理を終了したかどうかを示します。タスクが完了した場合は

true

を返し、それ以外の場合は

false

を返します。

計算から実際の結果を返すメソッドは


Future.get()


です。 。

このメソッドはタスクが完了するまで実行をブロックしますが、この例では

isDone()

を呼び出してタスクが完了したかどうかを最初に確認するので、これは問題になりません。

これら2つの方法を使用することで、メインタスクが終了するのを待つ間に他のコードを実行することができます。

Future<Integer> future = new SquareCalculator().calculate(10);

while(!future.isDone()) {
    System.out.println("Calculating...");
    Thread.sleep(300);
}

Integer result = future.get();

この例では、プログラムが計算を実行していることをユーザーに知らせるために、出力に簡単なメッセージを書きます。

メソッド

get()

はタスクが完了するまで実行をブロックします。

ただし、この例ではタスクが終了したことを確認した後に

get()

が呼び出されるようになるまでは、心配する必要はありません。したがって、このシナリオでは、

future.get()

は常にただちに戻ります。


get()

には、タイムアウトとhttps://docs.oracle.com/javase/8/docs/api/java/util/concurrent/TimeUnit.html[

TimeUnit

]として過負荷のバージョンがあります引数:

Integer result = future.get(500, TimeUnit.MILLISECONDS);



get(long、TimeUnit)の違い


および


get()


、前者は

httpsをスローします。//docs.oracle.com/javase/8/docs/api/java/util/concurrent/TimeoutException.html[TimeoutException]

指定されたタイムアウト期間内にタスクが戻らない場合。


3.2.

cancel()




Future

をキャンセルする

タスクを起動したとしますが、何らかの理由で、結果にはもう気にしません。


Future.cancel(boolean)


を使用して、エグゼキュータに停止を指示できます。操作とその基礎となるスレッドへの割り込み

Future<Integer> future = new SquareCalculator().calculate(4);

boolean canceled = future.cancel(true);

上記のコードからの

Future

のインスタンスは、その操作を完了することはありません。実際、

cancel()

を呼び出した後に、そのインスタンスから

get()

を呼び出そうとすると、結果は


https://docs.oracle.com/javase/8/docs/api/java/になります。

util/concurrent/CancellationException.html[CancellationException]



Future.isCancelled()


は、

Future

がすでにキャンセルされているかどうかを教えてくれます。これは

CancellationException

が発生しないようにするのに非常に便利です。


cancel()

の呼び出しが失敗する可能性があります。その場合、その戻り値は

false

になります。

cancel()



boolean

値を引数として取ることに注意してください – これはこのタスクを実行しているスレッドが割り込まれるべきかどうかを制御します。


4

Thread

プールを使用したその他のマルチスレッド


Executors.newSingleThreadExecutor

で取得されているため、現在の

ExecutorService

はシングルスレッドです。

この「シングルスレッド性」を強調するために、2つの計算を同時にトリガーしましょう。

SquareCalculator squareCalculator = new SquareCalculator();

Future<Integer> future1 = squareCalculator.calculate(10);
Future<Integer> future2 = squareCalculator.calculate(100);

while (!(future1.isDone() && future2.isDone())) {
    System.out.println(
      String.format(
        "future1 is %s and future2 is %s",
        future1.isDone() ? "done" : "not done",
        future2.isDone() ? "done" : "not done"
      )
    );
    Thread.sleep(300);
}

Integer result1 = future1.get();
Integer result2 = future2.get();

System.out.println(result1 + " and " + result2);

squareCalculator.shutdown();

それでは、このコードの出力を分析しましょう。

calculating square for: 10
future1 is not done and future2 is not done
future1 is not done and future2 is not done
future1 is not done and future2 is not done
future1 is not done and future2 is not done
calculating square for: 100
future1 is done and future2 is not done
future1 is done and future2 is not done
future1 is done and future2 is not done
100 and 10000

プロセスが並行していないことは明らかです。最初のタスクが完了すると2番目のタスクが開始され、プロセス全体が完了するまでに約2秒かかることに注意してください。

私たちのプログラムを本当にマルチスレッド化するためには、異なる種類の

ExecutorService

を使うべきです。ファクトリメソッド


https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/Executors.html#newFixedThreadPoolによって提供されるスレッドプールを使用すると、例の動作がどのように変わるかを見てみましょう。

-int-[Executors.newFixedThreadPool()]

:

public class SquareCalculator {

    private ExecutorService executor = Executors.newFixedThreadPool(2);

   //...
}


SquareCalculator

クラスを簡単に変更するだけで、2つの同時スレッドを使用できるexecutorができます。

まったく同じクライアントコードをもう一度実行すると、次のような出力が得られます。

calculating square for: 10
calculating square for: 100
future1 is not done and future2 is not done
future1 is not done and future2 is not done
future1 is not done and future2 is not done
future1 is not done and future2 is not done
100 and 10000

これは今とても良く見えています。 2つのタスクが同時に実行を開始および終了し、プロセス全体が完了するまでに約1秒かかることに注目してください。



Executors.newCachedThreadPool

のように、スレッドプールの作成に使用できる他のファクトリメソッドがあります。以前に使用された


Thread


sが使用可能な場合はそれを再利用し、


エグゼキュータ

newScheduledThreadPool()]

指定された遅延の後に実行するコマンドをスケジュールします


.

__


ExecutorService

の詳細については、このトピック専用の/java-executor-service-tutorial[記事]を参照してください。


5

ForkJoinTask


の概要



ForkJoinTask


は、

Future

を実装し、によってホストされる多数のタスクを実行できる抽象クラスです。


ForkJoinPool


にある少数の実際のスレッド。

このセクションでは、

ForkJoinPool

の主な特性について簡単に説明します。このトピックに関する包括的なガイドについては、リンクを確認してください:/java-fork-join[JavaにおけるFork/Joinフレームワークのガイド]

その場合、

ForkJoinTask

の主な特徴は、そのメインタスクを完了するために必要な作業の一部として、通常は新しいサブタスクを生成することです。


fork()


を呼び出すことで、新しいタスクが生成され、__httpsですべての結果が収集されます。 ://docs.oracle.com/javase/8/docs/api/java/util/concurrent/ForkJoinTask.html#join–[join()]、したがってクラスの名前。


ForkJoinTask

を実装する2つの抽象クラスがあります。



RecursiveTask


は完了時に値を返します。何も返さない/8/docs/api/java/util/concurrent/RecursiveAction.html[RecursiveAction]__名前が示すように、これらのクラスは、ファイルシステムのナビゲーションや複雑な数学的計算など、再帰的なタスクに使用されます。

前の例を拡張して、

Integer

を指定すると、そのすべての階乗要素の二乗和を計算するクラスを作成しましょう。したがって、たとえば、計算機に数字4を渡すと、42 32 22 12の合計から30の結果が得られます。

まず最初に、

RecursiveTask

の具体的な実装を作成し、その

compute()

メソッドを実装する必要があります。これが、ビジネスロジックを書く場所です。

public class FactorialSquareCalculator extends RecursiveTask<Integer> {

    private Integer n;

    public FactorialSquareCalculator(Integer n) {
        this.n = n;
    }

    @Override
    protected Integer compute() {
        if (n <= 1) {
            return n;
        }

        FactorialSquareCalculator calculator
          = new FactorialSquareCalculator(n - 1);

        calculator.fork();

        return n **  n + calculator.join();
    }
}


compute()

内に

FactorialSquareCalculator

の新しいインスタンスを作成することによって、再帰性がどのように達成されるかに注目してください。ノンブロッキングメソッドである

fork()

を呼び出すことで、

ForkJoinPool

にこのサブタスクの実行を開始するよう依頼します。


join()

メソッドはその計算の結果を返し、それに現在訪れている数の2乗を加えます。

実行とスレッド管理を処理するための

ForkJoinPool

を作成する必要があります。

ForkJoinPool forkJoinPool = new ForkJoinPool();

FactorialSquareCalculator calculator = new FactorialSquareCalculator(10);

forkJoinPool.execute(calculator);


6. 結論

この記事では、私たちは

Future

インターフェースを包括的に見て、そのすべてのメソッドを調べました。また、スレッドプールの機能を活用して複数の並列操作をトリガーする方法も学びました。

ForkJoinTask

クラスの主なメソッド、

fork()

および

join()

についても簡単に説明しました。

Javaでの並列操作と非同期操作に関する優れた記事が他にもたくさんあります。これは

Future

インターフェースに密接に関連しているそれらのうちの3つです(それらのうちのいくつかはすでに記事で述べられています):

Java 8で導入された多くの追加機能を備えた

Future

の実装
**

JavaにおけるFork/Joinフレームワークの手引き

– more

セクション5で取り上げた

ForkJoinTask

について
**

Javaへのガイド


ExecutorService

] –

ExecutorService

インターフェース専用


GitHub repository

でこの記事で使用されているソースコードを確認してください。