タスクの送信: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フィードバックを取得できます。ローカル設定は不要です。
このコースのすべてのレッスン
- ExecutorService とスレッドプールの種類
- タスクの送信:Runnable と Callable
- Future とエラー処理
- 定期タスクのための ScheduledExecutorService