0Pricing
Java Academy · レッスン

タスクの送信:Runnable と Callable

Runnable と Callable のタスクを送信し、Future 経由で結果を取得します。

「タスクの送信:Runnable と Callable」はCoddyKit上の無料Java Academyレッスンです。 これはレッスン2/4です。 下記で完全なレッスンを無料で読むことができます。その後、ブラウザ内の組み込みコードエディタと24時間対応のAIチューターでハンズオン演習できます。 これはJava Academy学習パスの一部であり、ウェブとCoddyKitアプリ全体で進捗が同期されます。 Java Academyコースには全4レッスンが含まれています。

RunnableとCallable

ExecutorServiceに送信するタスクは、2つの関数型インターフェースで表します。

  • Runnable:戻り値がなく、検査例外を宣言できません
  • Callable<V>:型Vの結果を返し、検査例外をスローできます
import java.util.concurrent.*;

// Runnable — no return value
Runnable r = () -> System.out.println("Running");

// Callable<Integer> — returns a value
Callable<Integer> c = () -> {
    return 42; // or do computation
};

submit(Runnable)

submit(Runnable)はFuture<?>を返します。このFutureに対してget()を呼び出すとnullが返りますが、タスクが完了したことを確認できます(タスクが失敗した場合は例外がスローされます)。

ExecutorService pool = Executors.newFixedThreadPool(2);

Future<?> future = pool.submit(() ->
    System.out.println("Task done on " + Thread.currentThread().getName())
);

future.get(); // waits for completion; returns null
System.out.println("Task confirmed complete");
pool.shutdown();

submit(Callable<V>)

submit(Callable)はFuture<V>を返します。結果を取得するにはfuture.get()を呼び出します(完了するまでブロックします)。

ExecutorService pool = Executors.newFixedThreadPool(2);

Callable<Integer> sumTask = () -> {
    int sum = 0;
    for (int i = 1; i <= 100; i++) sum += i;
    return sum;
};

Future<Integer> future = pool.submit(sumTask);
Integer result = future.get(); // blocks until done
System.out.println("Sum 1..100 = " + result); // 5050
pool.shutdown();

invokeAllによる複数のCallableの送信

invokeAll()はCallableのコレクションを送信し、FutureのListを返します。戻る前にすべてのタスクが完了します。

List<Callable<String>> tasks = List.of(
    () -> "Result A",
    () -> "Result B",
    () -> "Result C"
);

ExecutorService pool = Executors.newFixedThreadPool(3);
List<Future<String>> futures = pool.invokeAll(tasks);

for (Future<String> f : futures) {
    System.out.println(f.get());
}
pool.shutdown();

invokeAny:最初に成功した結果

invokeAny()はCallableのコレクションを送信し、最初に正常完了したタスクの結果を返して、残りのタスクをキャンセルします。

List<Callable<String>> candidates = List.of(
    () -> { Thread.sleep(200); return "Slow"; },
    () -> { Thread.sleep(50);  return "Fast"; },
    () -> { Thread.sleep(100); return "Medium"; }
);

ExecutorService pool = Executors.newFixedThreadPool(3);
String winner = pool.invokeAny(candidates);
System.out.println("First: " + winner); // Fast
pool.shutdown();

execute()とsubmit()の違い

execute(Runnable)は実行するだけで、Futureを返しません。未捕捉例外ハンドラーを追加しない限り、例外は失われます。submit()はタスクをFutureでラップし、例外を捕捉してget()で取得できるようにします。

ExecutorService pool = Executors.newFixedThreadPool(2);

// execute: exception silently lost
pool.execute(() -> { throw new RuntimeException("Oops!"); });

// submit: exception captured in Future
Future<?> f = pool.submit(() -> { throw new RuntimeException("Oops!"); });
try {
    f.get();
} catch (ExecutionException e) {
    System.out.println("Caught: " + e.getCause().getMessage());
}
pool.shutdown();

検査例外を扱うCallable

Runnableとは異なり、Callableは検査例外をスローできます。そのため、I/O操作やデータベース操作に適しています。

Callable<String> dbQuery = () -> {
    // This compiles fine — checked exception is declared on Callable.call()
    if (Math.random() < 0.5) throw new java.sql.SQLException("DB error");
    return "row data";
};

Future<String> f = pool.submit(dbQuery);
try {
    String data = f.get();
} catch (ExecutionException e) {
    if (e.getCause() instanceof java.sql.SQLException) {
        System.out.println("DB failed: " + e.getCause().getMessage());
    }
}

独立した計算の並列化

複数のCallableを送信して、独立した計算を並列実行します。

ExecutorService pool = Executors.newFixedThreadPool(4);

Future<Long> sumFuture  = pool.submit(() -> LongStream.rangeClosed(1,1_000_000).sum());
Future<Long> prodFuture = pool.submit(() -> LongStream.rangeClosed(1,20).reduce(1L, (a,b)->a*b));

long sum  = sumFuture.get();
long prod = prodFuture.get();
System.out.println("Sum: " + sum + ", 20!: " + prod);
pool.shutdown();

Callableの結果の集約

多数のタスクを送信し、すべてのタスクが完了したら結果を集約します。

int N = 8;
ExecutorService pool = Executors.newFixedThreadPool(N);
List<Future<Integer>> futures = new ArrayList<>();

for (int i = 0; i < N; i++) {
    final int chunk = i;
    futures.add(pool.submit(() -> chunk * chunk)); // i^2
}

int total = 0;
for (Future<Integer> f : futures) total += f.get();
System.out.println("Sum of squares: " + total);
pool.shutdown();

Callableのタイムアウト

future.get(timeout, unit)にタイムアウトを指定して、いつまでも待ち続けることを防ぎます。

Future<String> f = pool.submit(() -> {
    Thread.sleep(5000); // slow task
    return "done";
});
try {
    String result = f.get(1, TimeUnit.SECONDS);
} catch (TimeoutException e) {
    System.out.println("Task timed out");
    f.cancel(true); // interrupt the task
} catch (ExecutionException e) {
    System.out.println("Task failed: " + e.getCause());
}

ベストプラクティス

ベストプラクティスのまとめ:

  • execute()よりsubmit()を使用します。例外が捕捉されるためです
  • 戻り値や検査例外が必要な場合はCallableを使用します
  • 必ずタイムアウトを指定してfuture.get()を呼び出します
  • バッチ処理にはinvokeAllを使用し、最初の応答を採用する処理にはinvokeAnyを使用します
  • 必ずプールをシャットダウンします

確認問題

Runnableに対するCallableの主な利点は何ですか?

復習:RunnableとCallable

重要なポイント:

  • Runnable:voidを返し、検査例外は使用できません。submitはFuture<?>を返し、結果はnullです
  • Callable:Vを返し、検査例外をスローできます。submitはFuture<V>を返します
  • invokeAll:タスクを一括送信し、すべての完了を待機します
  • invokeAny:最初に成功した結果を返し、残りをキャンセルします
  • 例外を捕捉するには、必ずsubmit()を使用します

よくある質問

「タスクの送信:Runnable と Callable」レッスンは無料ですか?

はい。「タスクの送信:Runnable と Callable」の完全なテキストはこのウェブで無料で読めます。インタラクティブに演習し(組み込みコードエディタと24時間対応のAIチューター)、Java Academyコースの残りをアンロックするには、CoddyKit PROにアップグレードしてください。 Java Academyコースには全4レッスンが含まれています。

「タスクの送信:Runnable と Callable」で何を学びますか?

Runnable と Callable のタスクを送信し、Future 経由で結果を取得します。 ブラウザで直接実行するハンズオンコードでJava Academyを演習し、24時間対応のAIチューターがレッスンを進める中での質問に答えます。

Java Academyを始めるのに経験は必要ですか?

事前経験は必要ありません。CoddyKitのJava Academyは初級者から上級者向けに構成されているため、ここから始めるか最初から始めて、自分のペースで進むことができます。 これはレッスン2/4です。

「タスクの送信:Runnable と Callable」レッスンにはどのくらい時間がかかりますか?

ほとんどのCoddyKitレッスンは約5~10分かかります。各レッスンはコンパクトでインタラクティブなので、着実に進歩し、ウェブとアプリ全体で正確に前回の場所から再開できます。

このJava Academyレッスンでコードを書いて実行できますか?

はい。すべてのJava Academyレッスンに組み込みコードエディタが含まれているため、ブラウザでリアルコードを書いて実行し、即座のAIフィードバックを取得できます。ローカル設定は不要です。

このコースのすべてのレッスン

  1. ExecutorService とスレッドプールの種類
  2. タスクの送信:Runnable と Callable
  3. Future とエラー処理
  4. 定期タスクのための ScheduledExecutorService
← Java Academyに戻る