JavaにおけるRunnableとCallable

実行可能vs. Javaで呼び出し可能

1. 概要

Javaの初期から、マルチスレッドはこの言語の主要な側面でした。 Runnableは、マルチスレッドタスクを表すために提供されるコアインターフェイスであり、Callableは、Java 1.5で追加された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 call() throws Exception;
}

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

public class FactorialTask implements Callable {
    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 future = executorService.submit(task);

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

4. 例外処理

それらが例外処理にどれほど適しているか見てみましょう。

4.1. Runnableを使用

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

4.2. Callableを使用

Callable’s call()メソッドには「throwsException”句」が含まれているため、チェックされた例外をさらに簡単に伝播できます。

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

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

Callable usingExecutorService,を実行する場合、例外はFutureオブジェクトに収集されます。これは、Future.get()メソッドを呼び出すことで確認できます。 これにより、元の例外をラップするExecutionException –がスローされます。

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

    FactorialCallableTask task = new FactorialCallableTask(-5);
    Future 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 future = executorService.submit(task);

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

パラメータの負の値の例外をFactorialCallableTask.にスローした場合でも、上記のテストは正常に合格します。

5. 結論

この記事では、RunnableインターフェースとCallableインターフェースの違いについて説明しました。

いつものように、この記事の完全なコードはover on GitHubで入手できます。