JavaのThreadとRunnableインターフェース
2025-08-03はじめに
現代のコンピュータは、複数の処理を同時に実行するマルチタスク能力を持っています。Javaでは、この同時実行を実現するためにスレッドという概念を使用します。この記事では、Javaにおけるスレッドの基本から実践的な使い方まで、初学者の方にもわかりやすく解説します。
Threadクラスの基本
スレッドの基本概念
スレッドとは、プログラム内の独立した実行の流れのことです。1つのプログラムで複数のスレッドを実行することをマルチスレッドと呼びます。
例えて言うと…
- レストランでの調理場を想像してください
- シングルスレッド:調理師が1人だけで、順番に料理を作る
- マルチスレッド:調理師が複数人いて、同時に複数の料理を作る
マルチスレッドのメリット
- 応答性の向上:UIスレッドがブロックされても、バックグラウンドで処理を続行できる
- リソースの有効活用:CPUのアイドル時間を減らし、効率を向上させる
- 処理の並行実行:複数のタスクを同時に処理できる
最も簡単なスレッドの作成方法
次のコードは、Threadクラスを継承して複数のスレッドを実行する基本例です。MyThreadクラスはThreadを継承し、run()メソッド内で1秒ごとにカウントを出力します。mainメソッドでは2つのスレッド(AとB)を生成してstart()で並行実行し、同時にメインスレッドも独自の処理を進めます。これにより、複数の処理が同時進行するマルチスレッドの仕組みを確認できます。
// Threadクラスを継承したカスタムスレッド
class MyThread extends Thread {
private String threadName;
// コンストラクタ
public MyThread(String name) {
this.threadName = name;
}
// runメソッドをオーバーライド - ここにスレッドで実行する処理を書く
@Override
public void run() {
try {
for (int i = 1; i <= 5; i++) {
System.out.println(threadName + " - カウント: " + i);
// 1秒間スリープ
Thread.sleep(1000);
}
} catch (InterruptedException e) {
System.out.println(threadName + "が中断されました");
}
System.out.println(threadName + "が終了しました");
}
}
public class BasicThreadExample {
public static void main(String[] args) {
System.out.println("メインスレッド開始");
// スレッドの作成
MyThread thread1 = new MyThread("スレッドA");
MyThread thread2 = new MyThread("スレッドB");
// スレッドの開始 - start()メソッドを呼び出す
thread1.start();
thread2.start();
// メインスレッドも処理を続行
try {
for (int i = 1; i <= 3; i++) {
System.out.println("メインスレッド - 処理: " + i);
Thread.sleep(500);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("メインスレッド終了");
}
}
実行結果の例
メインスレッド開始
メインスレッド - 処理: 1
スレッドA - カウント: 1
スレッドB - カウント: 1
メインスレッド - 処理: 2
メインスレッド - 処理: 3
メインスレッド終了
スレッドA - カウント: 2
スレッドB - カウント: 2
スレッドA - カウント: 3
スレッドB - カウント: 3
スレッドA - カウント: 4
スレッドB - カウント: 4
スレッドA - カウント: 5
スレッドB - カウント: 5
スレッドAが終了しました
スレッドBが終了しました
重要なポイント:
start()メソッドを呼び出すことで新しいスレッドが開始されるrun()メソッドを直接呼び出すと、新しいスレッドは作成されない(単なるメソッド呼び出し)- スレッドの実行順序は保証されない
Runnableインターフェースの使い方
Runnableインターフェースの基本
次のコードは、Runnableインターフェースを実装してスレッドを実行する基本例です。
MyRunnableクラスはRunnableを実装し、run()メソッド内で一定間隔ごとに処理内容を出力します。mainメソッドでは、このRunnableを引数にThreadオブジェクトを生成し、start()で2つのスレッドを並行実行します。さらに、join()を使って各スレッドの終了を待機し、すべての処理が完了した後にメッセージを出力します。
// Runnableインターフェースを実装したクラス
class MyRunnable implements Runnable {
private String taskName;
public MyRunnable(String name) {
this.taskName = name;
}
@Override
public void run() {
try {
for (int i = 1; i <= 5; i++) {
System.out.println(taskName + " - 実行中: " + i);
Thread.sleep(800);
}
} catch (InterruptedException e) {
System.out.println(taskName + "が中断されました");
}
System.out.println(taskName + "の実行が完了しました");
}
}
public class RunnableExample {
public static void main(String[] args) {
System.out.println("メインスレッド開始");
// Runnableオブジェクトの作成
Runnable task1 = new MyRunnable("タスクA");
Runnable task2 = new MyRunnable("タスクB");
// Threadオブジェクトの作成(Runnableを渡す)
Thread thread1 = new Thread(task1);
Thread thread2 = new Thread(task2);
// スレッドの開始
thread1.start();
thread2.start();
// メインスレッドで待機
try {
thread1.join(); // thread1の終了を待つ
thread2.join(); // thread2の終了を待つ
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("すべてのタスクが完了しました");
}
}
この例は、Runnableを使った柔軟で再利用性の高いマルチスレッド処理の基本形を示しています。
ラムダ式を使った簡潔な実装
次のコードは、ラムダ式とメソッド参照を使ってスレッドを作成・実行する例です。
thread1では、Runnableをラムダ式で直接実装し、1秒ごとにカウントを出力します。thread2では、LambdaThreadExample::backgroundTaskというメソッド参照を使い、別メソッドの処理をスレッドとして実行しています。
public class LambdaThreadExample {
public static void main(String[] args) {
System.out.println("ラムダ式を使ったスレッド例");
// ラムダ式でRunnableを実装
Thread thread1 = new Thread(() -> {
for (int i = 1; i <= 5; i++) {
System.out.println("ラムダスレッド - カウント: " + i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
break;
}
}
});
// メソッド参照を使用
Thread thread2 = new Thread(LambdaThreadExample::backgroundTask);
thread1.start();
thread2.start();
}
// バックグラウンドタスク用のメソッド
private static void backgroundTask() {
for (int i = 1; i <= 5; i++) {
System.out.println("メソッド参照スレッド - 値: " + (i * 10));
try {
Thread.sleep(800);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
break;
}
}
}
}
これにより、匿名クラスを使わずに簡潔な構文でスレッド処理を記述できることが示されており、モダンなJavaでのマルチスレッドの書き方の好例です。
スレッドの状態とライフサイクル
次のコードは、**Javaスレッドのライフサイクル(状態遷移)**を確認する例です。スレッドを生成してから終了するまでの各段階で、getState()を使ってスレッドの状態を出力しています。
- 作成直後は NEW(まだ開始していない状態)
start()呼び出し後は RUNNABLE(実行可能状態)sleep()中は TIMED_WAITING(一定時間待機中)- 実行完了後は TERMINATED(終了状態)
public class ThreadStateExample {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
try {
System.out.println("スレッド開始 - 状態: " + Thread.currentThread().getState());
Thread.sleep(2000);
System.out.println("スレッド実行中");
} catch (InterruptedException e) {
e.printStackTrace();
}
});
System.out.println("作成直後 - 状態: " + thread.getState()); // NEW
thread.start();
System.out.println("start()後 - 状態: " + thread.getState()); // RUNNABLE
Thread.sleep(100);
System.out.println("実行中 - 状態: " + thread.getState()); // TIMED_WAITING
thread.join();
System.out.println("終了後 - 状態: " + thread.getState()); // TERMINATED
}
}
このプログラムにより、スレッドが実行中にどのように状態を変化させるかを実際に観察できます。
スレッド状態の詳細
- NEW:スレッドが作成されたが、まだ開始されていない状態
- RUNNABLE:実行可能な状態
- BLOCKED:ロックの獲得待ちでブロックされている状態
- WAITING:他のスレッドからの通知を無限に待っている状態
- TIMED_WAITING:指定時間だけ待っている状態
- TERMINATED:実行が終了した状態
スレッドの制御と同期
スレッドの制御メソッド
次のコードは、スレッドの中断(interrupt)を制御する方法を示した例です。
workerスレッドは1~10までカウントしながら作業を行い、途中でThread.sleep(500)によって一時停止します。メインスレッドは3秒後にworker.interrupt()を呼び出して中断を通知し、スレッド側ではInterruptedExceptionをキャッチして「中断されました」と出力し、処理を終了します。
public class ThreadControlExample {
public static void main(String[] args) throws InterruptedException {
Thread worker = new Thread(() -> {
for (int i = 1; i <= 10; i++) {
System.out.println("作業中: " + i);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
System.out.println("中断されました");
return; // 早期終了
}
}
});
worker.start();
// 3秒待ってから中断
Thread.sleep(3000);
worker.interrupt(); // スレッドに中断を通知
worker.join();
System.out.println("作業終了");
}
}
このように、interrupt()と例外処理を組み合わせることで、安全にスレッドを停止・制御する方法を実現しています。
スレッドの同期(synchronized)
次のコードは、synchronizedを使ってスレッド間の競合を防ぐ方法を示しています。SharedCounterクラスでは、increment()とgetCount()メソッドにsynchronizedを付けることで、同時に1つのスレッドだけがカウント操作を実行できるようにしています。
mainメソッドでは10個のスレッドを生成し、それぞれが1000回カウントを増やします。synchronizedがないと結果が不正になる可能性がありますが、このコードでは排他制御により安全に実行され、最終カウントは確実に10000となります。
class SharedCounter {
private int count = 0;
// synchronizedメソッド - 同時に1つのスレッドのみが実行可能
public synchronized void increment() {
count++;
}
public synchronized int getCount() {
return count;
}
}
public class SynchronizedExample {
public static void main(String[] args) throws InterruptedException {
SharedCounter counter = new SharedCounter();
// 複数のスレッドが同時にカウンターをインクリメント
Thread[] threads = new Thread[10];
for (int i = 0; i < threads.length; i++) {
threads[i] = new Thread(() -> {
for (int j = 0; j < 1000; j++) {
counter.increment();
}
});
threads[i].start();
}
// すべてのスレッドの終了を待つ
for (Thread thread : threads) {
thread.join();
}
System.out.println("最終カウント: " + counter.getCount()); // 10000になる
}
}
つまり、共有データを複数スレッドで扱う際のスレッドセーフな実装例です。
実践的な使用例
例1:ファイルの並列ダウンロード
次のコードは、複数のファイルを並行してダウンロードするスレッド処理の例です。
FileDownloaderクラスはRunnableを実装し、各スレッドが異なるファイルのダウンロードをシミュレートします。進捗を20%ずつ表示し、完了時にはAtomicIntegerを使ってスレッドセーフに完了数をカウントしています。
mainメソッドでは複数のファイル名を配列で用意し、それぞれを独立したスレッド(Downloader-1〜Downloader-5)として実行します。join()を使って全スレッドの終了を待機し、全ダウンロード完了後にメッセージを出力します。
import java.util.concurrent.atomic.AtomicInteger;
class FileDownloader implements Runnable {
private String fileName;
private static AtomicInteger downloadCount = new AtomicInteger(0);
public FileDownloader(String fileName) {
this.fileName = fileName;
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "が " + fileName + " のダウンロードを開始");
try {
// ダウンロードのシミュレーション
for (int progress = 0; progress <= 100; progress += 20) {
System.out.println(fileName + " - " + progress + "% 完了");
Thread.sleep(500);
}
int count = downloadCount.incrementAndGet();
System.out.println(fileName + " のダウンロード完了! (完了数: " + count + ")");
} catch (InterruptedException e) {
System.out.println(fileName + " のダウンロードが中断されました");
}
}
}
public class FileDownloadExample {
public static void main(String[] args) {
String[] files = {
"document.pdf", "image.jpg", "video.mp4",
"music.mp3", "archive.zip"
};
System.out.println("ファイルダウンロード開始");
// 各ファイルのダウンロードを別スレッドで実行
Thread[] downloadThreads = new Thread[files.length];
for (int i = 0; i < files.length; i++) {
downloadThreads[i] = new Thread(new FileDownloader(files[i]), "Downloader-" + (i+1));
downloadThreads[i].start();
}
// すべてのダウンロードの完了を待つ
for (Thread thread : downloadThreads) {
try {
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("すべてのファイルのダウンロードが完了しました");
}
}
このコードは、マルチスレッドとAtomic変数による安全な並列処理の実践例です。
例2:生産者-消費者パターン
次のコードは、**スレッド間通信の代表例である「生産者―消費者(Producer–Consumer)パターン」**を実装したものです。MessageQueueクラスはメッセージを一時的に保持する共有キューで、produce()がメッセージを追加し、consume()がメッセージを取り出します。
synchronizedでスレッド安全を確保し、キューが満杯なら生産者はwait()で待機、空なら消費者が待機するように制御しています。notifyAll()を使って待機中のスレッドに再開を通知する仕組みも備えています。mainメソッドでは、生産者スレッドがメッセージを順次生成し、消費者スレッドがそれを取り出す処理を並行実行。
import java.util.LinkedList;
import java.util.Queue;
class MessageQueue {
private Queue queue = new LinkedList<>();
private int capacity;
public MessageQueue(int capacity) {
this.capacity = capacity;
}
public synchronized void produce(String message) throws InterruptedException {
while (queue.size() == capacity) {
wait(); // キューが満杯なら待機
}
queue.add(message);
System.out.println("生産: " + message + " (サイズ: " + queue.size() + ")");
notifyAll(); // 消費者に通知
}
public synchronized String consume() throws InterruptedException {
while (queue.isEmpty()) {
wait(); // キューが空なら待機
}
String message = queue.poll();
System.out.println("消費: " + message + " (サイズ: " + queue.size() + ")");
notifyAll(); // 生産者に通知
return message;
}
}
public class ProducerConsumerExample {
public static void main(String[] args) {
MessageQueue queue = new MessageQueue(5);
// 生産者スレッド
Thread producer = new Thread(() -> {
try {
for (int i = 1; i <= 10; i++) {
queue.produce("メッセージ-" + i);
Thread.sleep(200);
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});
// 消費者スレッド
Thread consumer = new Thread(() -> {
try {
for (int i = 1; i <= 10; i++) {
queue.consume();
Thread.sleep(300);
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});
producer.start();
consumer.start();
try {
producer.join();
consumer.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("生産者-消費者パターンの実行完了");
}
}
これにより、スレッド間の同期とデータ共有の基本的な仕組みを学べる実用的な例になっています。
例3:ExecutorServiceを使った高度なスレッド管理
次のコードは、ExecutorServiceを使ったスレッドプール処理の実践例です。CalculationTaskクラスはCallableを実装し、引数として与えられた数値をもとに**重い計算(平方+平方根)**を行い、その結果を返します。
mainメソッドでは、Executors.newFixedThreadPool(4)で4スレッドのプールを作成し、8つの計算タスクを同時並行で実行しています。各タスクの結果はFutureで受け取り、get()を呼ぶことで非同期計算の結果を取得します。すべての結果を合計した後、shutdown()でスレッドプールを安全に終了します。
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.Callable;
import java.util.concurrent.TimeUnit;
class CalculationTask implements Callable {
private int number;
public CalculationTask(int number) {
this.number = number;
}
@Override
public Double call() throws Exception {
System.out.println(Thread.currentThread().getName() + " が計算を開始: " + number);
// 重い計算のシミュレーション
Thread.sleep(1000);
double result = Math.pow(number, 2) + Math.sqrt(number);
System.out.println(Thread.currentThread().getName() + " が計算完了: " + result);
return result;
}
}
public class ExecutorServiceExample {
public static void main(String[] args) {
System.out.println("ExecutorServiceの使用例");
// スレッドプールの作成(4つのスレッド)
ExecutorService executor = Executors.newFixedThreadPool(4);
try {
// 複数のタスクを実行
Future[] results = new Future[8];
for (int i = 1; i <= 8; i++) {
CalculationTask task = new CalculationTask(i * 10);
results[i-1] = executor.submit(task);
}
// 結果の取得
double sum = 0;
for (int i = 0; i < results.length; i++) {
Double result = results[i].get(); // 結果が得られるまでブロック
sum += result;
System.out.println("タスク" + (i+1) + "の結果: " + result);
}
System.out.println("全結果の合計: " + sum);
} catch (Exception e) {
e.printStackTrace();
} finally {
// ExecutorServiceのシャットダウン
executor.shutdown();
try {
if (!executor.awaitTermination(60, TimeUnit.SECONDS)) {
executor.shutdownNow();
}
} catch (InterruptedException e) {
executor.shutdownNow();
}
}
}
}
この例は、複数のタスクを効率的に並行処理し、結果を集約する方法をわかりやすく示しています。
注意点とベストプラクティス
1. スレッドセーフティ
次のコードは、スレッドセーフと非スレッドセーフなカウンターの違いを示しています。
UnsafeCounterは通常のint変数を使用しており、count++がアトミック(不可分)な操作ではないため、複数のスレッドから同時に実行すると正しい値にならない可能性があります。
一方、SafeCounterはAtomicIntegerを使い、incrementAndGet()メソッドでスレッドセーフに値を更新しています。
// 非スレッドセーフな例
class UnsafeCounter {
private int count = 0;
public void increment() {
count++; // これはアトミックではない!
}
public int getCount() {
return count;
}
}
// スレッドセーフな例
class SafeCounter {
private AtomicInteger count = new AtomicInteger(0);
public void increment() {
count.incrementAndGet(); // アトミック操作
}
public int getCount() {
return count.get();
}
}
この方法では、内部的に排他制御が行われるため、マルチスレッド環境でも正確なカウントが保証されます。
2. デッドロックの回避
このコードは、2つのロックlock1とlock2を別々のスレッドが逆順に取得しようとしているため、互いに相手のロックの解放を待ち続けるデッドロックの典型例を示しています。thread1は先にlock1を、thread2は先にlock2をつかみ、その後もう一方のロックを待つ構造になっているため、状況によってはどちらのスレッドも先に進めなくなります。
public class DeadlockExample {
private static final Object lock1 = new Object();
private static final Object lock2 = new Object();
public static void main(String[] args) {
Thread thread1 = new Thread(() -> {
synchronized (lock1) {
System.out.println("Thread1: lock1を獲得");
try { Thread.sleep(100); } catch (InterruptedException e) {}
synchronized (lock2) {
System.out.println("Thread1: lock2を獲得");
}
}
});
Thread thread2 = new Thread(() -> {
synchronized (lock2) {
System.out.println("Thread2: lock2を獲得");
try { Thread.sleep(100); } catch (InterruptedException e) {}
synchronized (lock1) {
System.out.println("Thread2: lock1を獲得");
}
}
});
thread1.start();
thread2.start();
// デッドロックが発生する可能性あり!
}
}
3. ベストプラクティス
- Runnableインターフェースの使用:Threadクラスの継承よりも柔軟
- スレッドプールの使用:ExecutorServiceでリソース管理
- 適切な同期:synchronizedやAtomicクラスの使用
- インタラプトの適切な処理:
InterruptedExceptionの適切な処理 - リソースリークの防止:finallyブロックでのクリーンアップ
まとめ
Javaのスレッドプログラミングを理解するには、ThreadクラスとRunnableインターフェースの使い分け、スレッドのライフサイクルと状態遷移、同期の重要性とその実装方法、スレッドセーフなコードの書き方、そしてExecutorServiceを用いた高度なスレッド管理といった概念を総合的に理解することが重要です。
マルチスレッドプログラミングは複雑ですが、現代のアプリケーション開発では必須のスキルです。基本をしっかり理解し、実践を通じて経験を積むことが重要です。最初は簡単な例から始めて、徐々に複雑なパターンに挑戦していくことをお勧めします。
演習問題
初級問題(3問)
初級問題1:基本的なスレッド作成
問題: Threadクラスを継承して、1から5までを表示するスレッドを作成してください。2つのスレッドを同時に実行し、それぞれが数字を表示するプログラムを書いてください。
// ここにコードを記述
初級問題2:Runnableインターフェースの実装
問題: Runnableインターフェースを実装して、"Hello"と"World"を交互に5回表示するスレッドを作成してください。メインスレッドと並行して実行されるようにしてください。
// ここにコードを記述
初級問題3:ラムダ式でのスレッド作成
問題: ラムダ式を使用して、1から10までの偶数だけを表示するスレッドを作成してください。2秒間隔で数字を表示するようにしてください。
// ここにコードを記述
中級問題(6問)
中級問題1:スレッドの同期制御
問題: 2つのスレッドが共有カウンターをインクリメントするプログラムを作成してください。synchronizedを使用して、最終的なカウントが正しく2000になることを保証してください。
class SharedCounter {
private int count = 0;
// ここにsynchronizedメソッドを実装
}
// ここにテストコードを記述
中級問題2:スレッドの待機と通知
問題: 生産者-消費者パターンを実装してください。生産者スレッドがアイテムを生産し、消費者スレッドがそれを消費するプログラムを作成してください。キューサイズは5とします。
// ここにコードを記述
中級問題3:スレッドプールの使用
問題: ExecutorServiceを使用して、1から10までの数値の二乗を計算するタスクを4つのスレッドで実行するプログラムを作成してください。
// ここにコードを記述
中級問題4:スレッドの中断処理
問題: 長時間実行するタスクを作成し、3秒後にメインスレッドから中断するプログラムを作成してください。中断された場合は"タスクが中断されました"と表示してください。
// ここにコードを記述
中級問題5:アトミック変数の使用
問題: AtomicIntegerを使用して、10個のスレッドがそれぞれ100回ずつカウンターをインクリメントするプログラムを作成してください。最終的にカウントが1000になることを確認してください。
// ここにコードを記述
中級問題6:CallableとFutureの使用
問題: Callableインターフェースを使用して、数値の階乗を計算するタスクを作成してください。Futureを使用して結果を取得し、表示してください。
// ここにコードを記述
上級問題(3問)
上級問題1:デッドロックの発生と解決
問題: 2つのリソースと2つのスレッドを使用してデッドロックが発生するプログラムを作成し、その後でデッドロックを解決するバージョンを作成してください。
// デッドロックが発生するバージョン
// ここにコードを記述
// デッドロックを解決するバージョン
// ここにコードを記述
上級問題2:読み書きロックの実装
問題: ReadWriteLockを使用して、複数のリーダースレッドと単一のライタースレッドが共有リソースにアクセスするプログラムを作成してください。
// ここにコードを記述
上級問題3:CompletableFutureを使用した非同期処理チェーン
問題: CompletableFutureを使用して、以下の処理チェーンを実装してください:
- 数値の二乗を計算
- 結果に10を加算
- さらに2倍する
- 最終結果を表示
// ここにコードを記述
演習問題 解答例
初級問題1:基本的なスレッド作成
class NumberThread extends Thread {
private String threadName;
public NumberThread(String name) {
this.threadName = name;
}
@Override
public void run() {
for (int i = 1; i <= 5; i++) {
System.out.println(threadName + ": " + i);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class BasicThreadExample {
public static void main(String[] args) {
Thread thread1 = new NumberThread("スレッドA");
Thread thread2 = new NumberThread("スレッドB");
thread1.start();
thread2.start();
}
}
初級問題2:Runnableインターフェースの実装
class HelloWorldTask implements Runnable {
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println("Hello");
try {
Thread.sleep(1000);
System.out.println("World");
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class RunnableExample {
public static void main(String[] args) {
Thread thread = new Thread(new HelloWorldTask());
thread.start();
// メインスレッドも並行して実行
for (int i = 0; i < 5; i++) {
System.out.println("メインスレッド");
try {
Thread.sleep(1500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
初級問題3:ラムダ式でのスレッド作成
public class LambdaThreadExample {
public static void main(String[] args) {
Thread evenThread = new Thread(() -> {
for (int i = 2; i <= 10; i += 2) {
System.out.println("偶数: " + i);
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
evenThread.start();
}
}
中級問題1:スレッドの同期制御
class SharedCounter {
private int count = 0;
public synchronized void increment() {
count++;
}
public synchronized int getCount() {
return count;
}
}
public class SynchronizedCounter {
public static void main(String[] args) throws InterruptedException {
SharedCounter counter = new SharedCounter();
Thread[] threads = new Thread[2];
for (int i = 0; i < threads.length; i++) {
threads[i] = new Thread(() -> {
for (int j = 0; j < 1000; j++) {
counter.increment();
}
});
threads[i].start();
}
for (Thread thread : threads) {
thread.join();
}
System.out.println("最終カウント: " + counter.getCount()); // 2000
}
}
中級問題2:スレッドの待機と通知
import java.util.LinkedList;
import java.util.Queue;
class MessageQueue {
private Queue queue = new LinkedList<>();
private int capacity = 5;
public synchronized void produce(String item) throws InterruptedException {
while (queue.size() == capacity) {
wait();
}
queue.add(item);
System.out.println("生産: " + item + " (サイズ: " + queue.size() + ")");
notifyAll();
}
public synchronized String consume() throws InterruptedException {
while (queue.isEmpty()) {
wait();
}
String item = queue.poll();
System.out.println("消費: " + item + " (サイズ: " + queue.size() + ")");
notifyAll();
return item;
}
}
public class ProducerConsumerExample {
public static void main(String[] args) {
MessageQueue queue = new MessageQueue();
Thread producer = new Thread(() -> {
try {
for (int i = 1; i <= 10; i++) {
queue.produce("アイテム-" + i);
Thread.sleep(100);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
});
Thread consumer = new Thread(() -> {
try {
for (int i = 1; i <= 10; i++) {
queue.consume();
Thread.sleep(150);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
});
producer.start();
consumer.start();
}
}
中級問題3:スレッドプールの使用
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.ArrayList;
import java.util.List;
public class ThreadPoolExample {
public static void main(String[] args) {
ExecutorService executor = Executors.newFixedThreadPool(4);
List> results = new ArrayList<>();
for (int i = 1; i <= 10; i++) {
final int number = i;
Future future = executor.submit(() -> {
int square = number * number;
System.out.println(Thread.currentThread().getName() +
": " + number + "の二乗 = " + square);
return square;
});
results.add(future);
}
executor.shutdown();
}
}
中級問題4:スレッドの中断処理
public class InterruptExample {
public static void main(String[] args) throws InterruptedException {
Thread longRunningTask = new Thread(() -> {
try {
for (int i = 1; i <= 10; i++) {
if (Thread.currentThread().isInterrupted()) {
throw new InterruptedException();
}
System.out.println("処理中: " + i);
Thread.sleep(1000);
}
System.out.println("タスク完了");
} catch (InterruptedException e) {
System.out.println("タスクが中断されました");
}
});
longRunningTask.start();
Thread.sleep(3000);
longRunningTask.interrupt();
}
}
中級問題5:アトミック変数の使用
import java.util.concurrent.atomic.AtomicInteger;
public class AtomicExample {
public static void main(String[] args) throws InterruptedException {
AtomicInteger counter = new AtomicInteger(0);
Thread[] threads = new Thread[10];
for (int i = 0; i < threads.length; i++) {
threads[i] = new Thread(() -> {
for (int j = 0; j < 100; j++) {
counter.incrementAndGet();
}
});
threads[i].start();
}
for (Thread thread : threads) {
thread.join();
}
System.out.println("最終カウント: " + counter.get()); // 1000
}
}
中級問題6:CallableとFutureの使用
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
class FactorialTask implements Callable {
private int number;
public FactorialTask(int number) {
this.number = number;
}
@Override
public Long call() {
long result = 1;
for (int i = 2; i <= number; i++) {
result *= i;
}
return result;
}
}
public class CallableExample {
public static void main(String[] args) throws Exception {
ExecutorService executor = Executors.newSingleThreadExecutor();
Future future = executor.submit(new FactorialTask(5));
System.out.println("計算中...");
Long result = future.get();
System.out.println("5! = " + result);
executor.shutdown();
}
}
上級問題1:デッドロックの発生と解決
// デッドロックが発生するバージョン
class DeadlockExample {
private static final Object lock1 = new Object();
private static final Object lock2 = new Object();
public static void main(String[] args) {
Thread thread1 = new Thread(() -> {
synchronized (lock1) {
System.out.println("Thread1: lock1を獲得");
try { Thread.sleep(100); } catch (InterruptedException e) {}
synchronized (lock2) {
System.out.println("Thread1: lock2を獲得");
}
}
});
Thread thread2 = new Thread(() -> {
synchronized (lock2) {
System.out.println("Thread2: lock2を獲得");
try { Thread.sleep(100); } catch (InterruptedException e) {}
synchronized (lock1) {
System.out.println("Thread2: lock1を獲得");
}
}
});
thread1.start();
thread2.start();
}
}
// デッドロックを解決するバージョン
class DeadlockSolution {
private static final Object lock1 = new Object();
private static final Object lock2 = new Object();
public static void main(String[] args) {
Thread thread1 = new Thread(() -> {
synchronized (lock1) {
System.out.println("Thread1: lock1を獲得");
try { Thread.sleep(100); } catch (InterruptedException e) {}
synchronized (lock2) {
System.out.println("Thread1: lock2を獲得");
}
}
});
Thread thread2 = new Thread(() -> {
synchronized (lock1) { // 同じ順序でロックを獲得
System.out.println("Thread2: lock1を獲得");
try { Thread.sleep(100); } catch (InterruptedException e) {}
synchronized (lock2) {
System.out.println("Thread2: lock2を獲得");
}
}
});
thread1.start();
thread2.start();
}
}
上級問題2:読み書きロックの実装
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
class SharedResource {
private int data = 0;
private ReadWriteLock lock = new ReentrantReadWriteLock();
public void readData() {
lock.readLock().lock();
try {
System.out.println(Thread.currentThread().getName() + " が読み取り: " + data);
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.readLock().unlock();
}
}
public void writeData(int newData) {
lock.writeLock().lock();
try {
data = newData;
System.out.println(Thread.currentThread().getName() + " が書き込み: " + data);
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.writeLock().unlock();
}
}
}
public class ReadWriteLockExample {
public static void main(String[] args) {
SharedResource resource = new SharedResource();
// リーダースレッド(複数可)
for (int i = 0; i < 3; i++) {
new Thread(() -> {
for (int j = 0; j < 3; j++) {
resource.readData();
}
}, "Reader-" + i).start();
}
// ライタースレッド(単一)
new Thread(() -> {
for (int i = 1; i <= 3; i++) {
resource.writeData(i * 10);
}
}, "Writer").start();
}
}
上級問題3:CompletableFutureを使用した非同期処理チェーン
import java.util.concurrent.CompletableFuture;
public class CompletableFutureExample {
public static void main(String[] args) {
CompletableFuture.supplyAsync(() -> {
// 数値の二乗を計算
int number = 5;
int square = number * number;
System.out.println("二乗計算: " + square);
return square;
})
.thenApplyAsync(result -> {
// 結果に10を加算
int added = result + 10;
System.out.println("10を加算: " + added);
return added;
})
.thenApplyAsync(result -> {
// さらに2倍する
int doubled = result * 2;
System.out.println("2倍する: " + doubled);
return doubled;
})
.thenAcceptAsync(finalResult -> {
// 最終結果を表示
System.out.println("最終結果: " + finalResult);
})
.join(); // 非同期処理の完了を待つ
System.out.println("処理完了");
}
}