まめぞうの技術メモ

IT関連で勉強したことをメモします

JavaのCallableとFutureで、スレッドで実行した結果を受け取れるよ!

最近、Javaのスレッドの話ばっかり書いてます。

ここまで来たらスレッド極めたいところ。

今日もスレッドの話。

これまで紹介してきた方法では、スレッドは実行しっぱなし。

結果を受け取れてませんでした。

今日は、スレッドで実行した結果を受け取れる「Callable」「Future」を紹介します。

スレッドで結果をやりとりしよう。会話のように。
スレッドで結果をやりとりしよう。会話のように。

リターンを返すには、Callableを実装しよう

これまでは、Threadを継承したり、Runnableを実装したクラスを呼び出していましたが、リターンを返すには、Callableを実装したクラスを作りましょう。

こんな感じです。

public class クラス名 implements Callable<String等のreturnする型> {

    // callの中に実際の処理を書く。

    public String call(){

        // returnが返せる。
        return "スレッド側の処理は終了しました。";
    }
}

ポイントは、「implements Callable」の後にある「<>」の部分ですね。

この記載をジェネリクスといいます。「<>」の中に型を指定できます。

Callableが色んな型でreturnが出来るので、「<>」の中にreturnしたい型を入れます。

上記の例では、「スレッド側の処理は終了しました」という文字列を返すので、「<String>」とするべきですね。

整数を返す場合は、「<Int>」として下さいね。

呼び出しは、ExecutorServiceとFutureインターフェースを使おう

さて、Callableを実装したクラスを呼び出しには、ExecutorServiceFutureインターフェースを使いましょう。

ExecutorServicesubmitメソッドを使えば、カンタンにスレッドを開始できます。

結果は、Futureインターフェースの型で返ってきます。

Futureインターフェースは、スレッドが終わった後のreturnを入れる入れ物です。

未来に値が入ってくるから、Futureなんですかね?(よくわからないけど・・・)

実際のプログラムで書くとこんな感じです。

        // ExecutorServiceを生成
        ExecutorService ex = Executors.newSingleThreadExecutor();

        // Executorにスレッド ThreadRun5() の実行を依頼
        Future<ジェネリクスの型> futureResult = ex.submit(new スレッドのクラス名());

Futureインターフェースに値が返ってきたら、以下のようにgetメソッドで取り出して使います。

        // スレッドから戻ってきた値を受け取る。
        try {
            // Future型は「getメソッド」で取り出す必要あり。
            String result = futureResult.get();
        }catch (Exception e){
            // ここは例外処理です
            System.out.println(e);
        }

サンプルコードで実際に動かしてみる

さて、実際にサンプルコードを動かしてみましょう。

まずは、呼び出されるCallableを実装したクラスから。

1秒おきに「Thread側: *回目の実行です」と出力し、最後に「Thread側の処理は終了しました」とリターンします。

ThreadRun5.java

import java.util.concurrent.Callable;

/**
 * 呼び出される Callableを実装したクラス
 * 最後に「スレッド側の処理は終了しました」を
 * returnできることがポイント。
 */
public class ThreadRun5 implements Callable<String> {

    // callの中に実際の処理を書く。
    // 今回は、1秒おきに、「Thread側: * 回目の実行です」
    // を出力する
    public String call(){
        for(int i=0; i<3; i++){
            System.out.println("Thread側: " + i + "回目の実行です");
            try{
                Thread.sleep(1000);
            }catch(InterruptedException e){
                System.out.println(e);
            }
        }
        // 最後にreturnが返せる。
        return "スレッド側の処理は終了しました。";
    }
}

ThreadCall5.java

次は、呼び出し側のプログラムです。

ExecutorServiceでThreadRun5の起動、Futureインターフェースで値の受け取りをしています。

こちらも1秒おきに「メイン側: *回目の実行です」と表示して、最後に「メイン側の処理は終了しました」と表示します。

合間に、ThreadRun5から返ってきたメッセージ「Thread側の処理は終了しました」も表示します。

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

public class ThreadCall5 {

    public static void main(String[] args){

        // ExecutorServiceを生成
        ExecutorService ex = Executors.newSingleThreadExecutor();

        // Executorにスレッド ThreadRun5() の実行を依頼
        Future<String> futureResult = ex.submit(new ThreadRun5());

        // スレッドとの並行処理がわかりやすいように、
        // メイン側でもfor文で「メイン側: *回目の実行です」
        // を出力
        for(int i=0; i<3; i++){
            System.out.println("メイン側: " + i + "回目の実行です");
            try{
                Thread.sleep(1000);
            }catch(InterruptedException e){
                System.out.println(e);
            }
        }

        // スレッドから戻ってきた値を受け取る。
        try {
            // Future型は「getメソッド」で取り出す必要あり。
            String result = futureResult.get();
            System.out.println(result);
        }catch (Exception e){
            System.out.println(e);
        }

        System.out.println("メイン側の処理は終了しました。");

        // 最後にexit(0)で正常終了。
        System.exit(0);
    }
}

プログラムの実行結果

プログラムを動かしてみると、こんな感じになります。

callableとFutureの実行結果

平行でメイン側とThread側のメッセージが表示されつつ、「スレッド側の処理は終了しました」というCallableが返したメッセージが表示されているのがわかると思います。

CallableにFutureインターフェース、使いこなせると、一歩上達できそうですね!

以上、ご参考でした。