1概要

Javaの初期の頃から、マルチスレッドは言語の主要な側面です。

Runnable

は、マルチスレッドタスクを表すために提供されているコアインタフェースです。

この記事では、両方のインターフェースの違いと用途について説明します。


2実行メカニズム

どちらのインタフェースも、複数のスレッドによって実行できるタスクを表すように設計されています。

Runnable

タスクは

Thread

クラスまたは

ExecutorService

を使用して実行できますが、

Callables

は後者を使用した場合のみ実行できます。


3返り値

これらのインターフェースが戻り値を処理する方法を詳しく見てみましょう。


3.1.

Runnable


付き


Runnable

インタフェースは機能的なインタフェースであり、パラメータを一切受け入れず、値を返さない単一の

run()

メソッドがあります。

これは、スレッド実行の結果を探していない状況、たとえば着信イベントのログ記録に適しています。

public interface Runnable {
    public void run();
}

例でこれを理解しましょう。

public class EventLoggingTask implements  Runnable{
    private Logger logger
      = LoggerFactory.getLogger(EventLoggingTask.class);

    @Override
    public void run() {
        logger.info("Message");
    }
}

この例では、スレッドは単にキューからメッセージを読み取り、それをログファイルに記録します。タスクから返される値はありません。タスクは__ExecutorServiceを使用して起動できます。

public void executeTask() {
    executorService = Executors.newSingleThreadExecutor();
    Future future = executorService.submit(new EventLoggingTask());
    executorService.shutdown();
}

この場合、

Future

オブジェクトは値を保持しません。


3.2.

Callable


付き


Callable

インターフェースは、単一の

call()

メソッドを含む汎用インターフェースです。これは汎用値

V

を返します。

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

数値の階乗の計算を見てみましょう。

public class FactorialTask implements Callable<Integer> {
    int number;

   //standard constructors

    public Integer call() throws InvalidParamaterException {
        int fact = 1;
       //...
        for(int count = number; count > 1; count--) {
            fact = fact **  count;
        }

        return fact;
    }
}


call()

メソッドの結果は、

Future

オブジェクト内に返されます。

@Test
public void whenTaskSubmitted__ThenFutureResultObtained(){
    FactorialTask task = new FactorialTask(5);
    Future<Integer> future = executorService.submit(task);

    assertEquals(120, future.get().intValue());
}


4例外処理

例外処理にどれだけ適しているかを見てみましょう。


4.1.

Runnable


付き

  • メソッドシグネチャには“ throws”節が指定されていないため、____さらにチェックされた例外を伝播する方法はありません。


4.2.

Callable


付き


Callableのcall()

メソッドには“ throws

Exception”

句が含まれているので、チェック済みの例外をさらに簡単に継承できます。

public class FactorialTask implements Callable<Integer> {
   //...
    public Integer call() throws InvalidParamaterException {

        if(number < 0) {
            throw new InvalidParamaterException("Number should be positive");
        }
   //...
    }
}


ExecutorServiceを使用して

Callableを実行する場合、例外は

Future

オブジェクトに収集されます。これは、

Future.get()メソッドを呼び出すことで確認できます。これは、元の例外をラップする

ExecutionException –__をスローします。

@Test(expected = ExecutionException.class)
public void whenException__ThenCallableThrowsIt() {

    FactorialCallableTask task = new FactorialCallableTask(-5);
    Future<Integer> future = executorService.submit(task);
    Integer result = future.get().intValue();
}

上記のテストでは、無効な数値を渡しているために

ExecutionException

がスローされています。この例外オブジェクトで

getCause()

メソッドを呼び出して、元のチェック済み例外を取得できます。


Future

クラスの

get()

メソッドを呼び出さないと、

call()

メソッドによってスローされた例外は報告されず、タスクは完了としてマークされます。

@Test
public void whenException__ThenCallableDoesntThrowsItIfGetIsNotCalled(){
    FactorialCallableTask task = new FactorialCallableTask(-5);
    Future<Integer> future = executorService.submit(task);

    assertEquals(false, future.isDone());
}

__FactorialCallableTask.にパラメータの負の値に対する例外をスローしたとしても、上記のテストは成功します


5結論

この記事では、

Runnable



Callable

の違いについて説明しました。

いつものように、この記事の完全なコードはhttps://github.com/eugenp/tutorials/tree/master/core-java-concurrency[GitHubで利用可能]です。