ランナブルの問題

Runnableインターフェイスとそれを実装するThreadクラスについてはすでによく理解しています。このインターフェースがどのようなものかを思い出してみましょう。


public interface Runnable {
	public abstract void run();
}

runメソッドの戻り値の型はvoidであることに注意してください。しかし、スレッドがその作業の結果を数値、文字列、またはその他のオブジェクトの形式で返す必要がある場合はどうなるでしょうか? 次に、回避策を考え出す必要があります。このようなもの:


public class Fibonacci implements Runnable {
 
 
 
	private final int index;
 
	private int result;
 
 
 
	public Fibonacci(int index) {
 
    		this.index = index;
 
	}
 
 
 
	@Override
 
	public void run() {
 
    		int first = 0;
 
    		int second = 1;
 
    		if (index == 1) {
 
        			result = first;
 
    		} else if (index == 2) {
 
        			result = second;
 
    		} else {
 
        			for (int i = 0; i < index - 2; i++) {
 
            				int temp = second;
 
            				second += first;
 
            				first = temp;
 
        			}
 
 
 
            			result = second;
 
    		}
 
	}
 
 
 
	public static void printByIndex(int index) throws InterruptedException {
 
    		Fibonacci fibonacci = new Fibonacci(index);
 
    		Thread thread = new Thread(fibonacci);
 
    		thread.start();
 
    		thread.join();
 
    		System.out.println("Fibonacci number " + index + ": " + fibonacci.result);
 
	}
 
}

次のmainメソッドを実行してみましょう。


	public static void main(String[] args) throws Exception {
    		Fibonacci.printByIndex(10);
	}

コンソールには以下が表示されます。

フィボナッチ数 10: 34

このコードにはいくつかの欠点があります。たとえば、 joinメソッドの呼び出しの結果、 printByIndexメソッドの実行中にメイン スレッドがブロックされます。

呼び出し可能なインターフェース

次に、Java が提供する、すぐに使用できるインターフェースを見てみましょう。これは、Runnableの代替として使用できます。これはCallableインターフェイスです。


public interface Callable<V> {
 
	V call() throws Exception;
 
}

ご覧のとおり、Runnableと同様に、メソッドは 1 つだけです。このメソッドはrunメソッドと同じ目的を果たします。これには、並列スレッドで実行されるコードが含まれています。違いについては、戻り値を見てください。これで、インターフェイスの実装時に指定した任意の型を指定できるようになります。


public class CurrentDate implements Callable<Long> {
 
	@Override
 
	public Long call() {
 
    		return new Date().getTime();
 
	}
 
}

もう一つの例:


Callable<String> task = () -> {
 
	Thread.sleep(100);
 
	return "Done";
 
};

他にも便利な点があります。callメソッドはExceptionをスローできます。つまり、runメソッドとは異なり、callメソッドではメソッド内で発生するチェック例外を処理する必要がありません。


public class Sleep implements Runnable {

	@Override

	public void run() {

    	    try {

        	        Thread.sleep(1000);

    	    } catch (InterruptedException ignored) {

    	    }

	}

}

public class Sleep implements Callable {

	@Override

	public Object call() throws InterruptedException {

    	    Thread.sleep(1000);

    	    return null;

	}

}

将来のインターフェース

Callableと緊密に連携するもう 1 つのインターフェイスはFutureです。Future は、非同期 (並列) 計算の結果 (呼び出しメソッドによって返される値) を表します。計算が完了したかどうかを確認したり、計算が完了するまで待機したり、計算結果を取得したりすることができます。

未来のメソッドインターフェイス

  • boolean isDone() — このメソッドは、このタスク (計算) が完了した場合にtrueを返します。正常に終了したタスク、例外で終了したタスク、またはキャンセルされたタスクは、完了したとみなされます。

  • V get() — 必要に応じて、このメソッドは呼び出し元のスレッドをブロックし、計算が完了するとその結果を返します。

  • V get(long timeout, TimeUnit Unit) — 前のメソッドと同様に、このメソッドは呼び出し元のスレッドをブロックし、結果を待ちますが、その期間はメソッド パラメーターで指定された時間だけです。

  • boolean cancel(boolean mayInterruptIfRunning) — このメソッドはタスクの実行を停止しようとします。タスクがまだ実行を開始していない場合、タスクは実行されません。タスクが進行中の場合、mayInterruptIfRunningパラメータは、タスクを実行しているスレッドを中断するかどうかを決定します。cancelメソッドが呼び出された後、 isDoneメソッドは常にtrueを返します。

  • boolean isCancelled() — このメソッドは、タスクが正常に終了する前にキャンセルされた場合にtrueを返します。cancelメソッドが以前に呼び出され、trueを返した場合、このメソッドは常にtrueを返します。

Callable と Future を使用したコードの例


import java.util.HashMap;
 
import java.util.Map;
 
import java.util.concurrent.*;
 
 
 
public class Fibonacci implements Callable<Integer> {
 
 
 
	private final int index;
 
 
 
	public Fibonacci(int index) {
 
    		this.index = index;
 
	}
 
 
 
	@Override
 
	public Integer call() {
 
    		int first = 0;
 
    		int second = 1;
 
    		if (index == 1) {
 
        			return first;
 
    		} else if (index == 2) {
 
        			return second;
 
    		} else {
 
        		for (int i = 0; i < index - 2; i++) {
 
            			int temp = second;
 
            			second += first;
 
            			first = temp;
 
        		}
 
 
 
        			return second;
 
    		}
 
	}
 
 
 
	public static Future<Integer> calculateAsync(int index) throws Exception {
 
    		Fibonacci fibonacci = new Fibonacci(index);
 
 
 
    		// The future object will represent the result of running the fibonacci task.
 
    		FutureTask<Integer> future = new FutureTask<>(fibonacci);
 
 
 
    		// Because the FutureTask class implements both the Future interface and the Runnable interface,
 
	 	// you can pass instances of it to the Thread constructor
 
    		Thread thread = new Thread(future);
 
    		thread.start();
 
 
 
    		return future;
 
	}
 
}

次のmainメソッドを実行してみましょう。


	public static void main(String[] args) throws Exception {
    		Map<Integer, Future<Integer>> tasks = new HashMap<>();
    		for (int i = 10; i < 20; i++) {
        			tasks.put(i, Fibonacci.calculateAsync(i));
    		}
 
    		for (Map.Entry<Integer, Future<Integer>> entry : tasks.entrySet()) {
        			Future<Integer> task = entry.getValue();
        			int index = entry.getKey();
        			int result;
        			// Check whether the task is done
        			if (task.isDone()) {
            				// Get the result of the calculations
            				result = task.get();
        			} else {
            				try {
                				// Wait another 100 milliseconds for the result of the calculations
                				result = task.get(100, TimeUnit.MILLISECONDS);
            				} catch (TimeoutException e) {
                				// Interrupt the task
                				task.cancel(true);
                				System.out.println("Fibonacci number " + index + " could not be calculated in the allotted time.");
                				return;
            				}
        			}
        			System.out.println("Fibonacci number " + index + ": " + result);
    		}
	}

コンソールには以下が表示されます。

フィボナッチ数 16: 610
フィボナッチ数 17: 987
フィボナッチ数 18: 1597
フィボナッチ数 19: 2584
フィボナッチ数 10: 34
フィボナッチ数 11: 55 フィボナッチ数 12:
89
フィボナッチ数 13: 144
フィボナッチ数 14: 233
フィボナッチ数 15: 377