1. スレッドのライフサイクルと状態遷移
Javaのスレッドは、生成から終了まで複数の状態を経て遷移します。これらの状態はjava.lang.Thread.State列挙型で定義されており、以下の6種類があります:
- NEW:スレッドが生成されたが、まだ
start()が呼び出されていない状態。 - RUNNABLE:
start()実行後、CPUスケジューリング待ちまたは実行中の状態。 - BLOCKED:他のスレッドが保持するロックを待機している状態。
- WAITING:
wait()やjoin()で無期限に待機している状態。 - TIMED_WAITING:
sleep()やwait(timeout)で指定時間待機している状態。 - TERMINATED:スレッド処理が正常終了した状態。
状態遷移の実践例
TIMED_WAITING遷移の確認:
public class ThreadLifecycleDemo {
public static void main(String[] args) throws InterruptedException {
Thread worker = new Thread(() -> {
System.out.println("RUNNABLE: " + Thread.currentThread().getState());
try { Thread.sleep(100); }
catch (InterruptedException e) { e.printStackTrace(); }
System.out.println("再びRUNNABLE: " + Thread.currentThread().getState());
});
System.out.println("初期状態: " + worker.getState());
worker.start();
Thread.sleep(50);
System.out.println("TIMED_WAITING中: " + worker.getState());
Thread.sleep(100);
System.out.println("終了状態: " + worker.getState());
}
}
WAITING遷移の確認:
public class WaitingStateDemo {
public static void main(String[] args) throws InterruptedException {
Object monitor = new Object();
Thread waiter = new Thread(() -> {
synchronized (monitor) {
try {
Thread.sleep(100);
monitor.wait();
} catch (InterruptedException e) { e.printStackTrace(); }
}
System.out.println("復帰後: " + Thread.currentThread().getState());
});
System.out.println("初期: " + waiter.getState());
waiter.start();
Thread.sleep(150);
System.out.println("WAITING中: " + waiter.getState());
new Thread(() -> {
synchronized (monitor) { monitor.notify(); }
}).start();
Thread.sleep(10);
System.out.println("終了: " + waiter.getState());
}
}
BLOCKED遷移の確認:
public class BlockedStateDemo {
public static void main(String[] args) throws InterruptedException {
Object lock = new Object();
new Thread(() -> {
synchronized (lock) {
try {
Thread.sleep(100);
lock.wait();
} catch (InterruptedException e) { e.printStackTrace(); }
}
}).start();
Thread blocker = new Thread(() -> {
System.out.println("RUNNABLE: " + Thread.currentThread().getState());
synchronized (lock) {
try { Thread.sleep(100); }
catch (InterruptedException e) { e.printStackTrace(); }
}
System.out.println("BLOCK解除: " + Thread.currentThread().getState());
});
System.out.println("初期: " + blocker.getState());
blocker.start();
Thread.sleep(50);
System.out.println("BLOCKED中: " + blocker.getState());
Thread.sleep(300);
System.out.println("終了: " + blocker.getState());
}
}
2. スレッドプールの活用
頻繁なスレッド生成・破棄はシステムリソースを浪費します。スレッドプールは事前にスレッドを準備し、タスク完了後も再利用することで効率を向上させます。
JDK標準のスレッドプール
import java.util.concurrent.*;
public class ThreadPoolExamples {
public static void main(String[] args) {
// キャッシュ型(必要に応じて動的に増減)
ExecutorService cached = Executors.newCachedThreadPool();
// 固定サイズ
ExecutorService fixed = Executors.newFixedThreadPool(5);
// カスタム設定
ThreadPoolExecutor custom = new ThreadPoolExecutor(
2, 8, 60L, TimeUnit.SECONDS,
new LinkedBlockingQueue<>(100),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.CallerRunsPolicy()
);
for (int i = 0; i < 3; i++) {
final int taskId = i;
custom.submit(() -> {
System.out.println("タスク" + taskId + "を" +
Thread.currentThread().getName() + "で実行");
});
}
custom.shutdown();
}
}
拒否ポリシーの比較
| ポリシー | 動作 |
|---|---|
| AbortPolicy | 例外を投げて拒否(デフォルト) |
| DiscardPolicy | 黙って破棄 |
| DiscardOldestPolicy | 最も古いタスクを破棄して新規追加 |
| CallerRunsPolicy | 呼び出し元スレッドで直接実行 |
3. 原子性と同期制御
複数スレッドが共有データを操作する際、中途半端な状態で他のスレッドに割り込まれるとデータ不整合が発生します。
volatileの限界と解決策
public class AtomicCounter {
private volatile int counter = 0; // volatileだけでは不十分
// 解決策1: synchronizedで排他制御
public synchronized void incrementSync() {
counter++;
}
// 解決策2: AtomicIntegerを使用
private AtomicInteger atomicCounter = new AtomicInteger(0);
public void incrementAtomic() {
atomicCounter.incrementAndGet();
}
}
AtomicIntegerの内部動作
AtomicIntegerはCAS(Compare-And-Swap)アルゴリズムと自旋(spin-wait)を組み合わせて実装されています:
// 擬似コード
public final int incrementAndGet() {
int current;
do {
current = get(); // 現在値を取得
} while (!compareAndSet(current, current + 1)); // CASで更新
return current + 1;
}
4. 並行コレクションと同期ユーティリティ
ConcurrentHashMapの利点
従来のHashtableは全体をロックするため性能が悪く、HashMapはスレッド安全ではありません。ConcurrentHashMapはセグメント単位のロック(Java7)やノード単位のロック(Java8)で高い並行性を実現します。
CountDownLatchによる同期
public class TaskCoordinator {
public static void main(String[] args) throws InterruptedException {
CountDownLatch latch = new CountDownLatch(3);
// 複数のワーカースレッド
for (int i = 1; i <= 3; i++) {
new Thread(() -> {
try {
Thread.sleep((long)(Math.random() * 2000));
System.out.println(Thread.currentThread().getName() + " 完了");
} finally {
latch.countDown(); // カウントダウン
}
}, "Worker-" + i).start();
}
// メインスレッドは全ワーカーの完了を待機
latch.await();
System.out.println("全タスク完了 - 後続処理開始");
}
}
Semaphoreによるリソース制限
public class ResourceLimiter {
private static final Semaphore SEMAPHORE = new Semaphore(2); // 同時実行数制限
public static void main(String[] args) {
for (int i = 1; i <= 5; i++) {
new Thread(() -> {
try {
SEMAPHORE.acquire(); // 許可を取得
System.out.println(Thread.currentThread().getName() + " リソース使用開始");
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName() + " リソース解放");
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
SEMAPHORE.release(); // 許可を返却
}
}, "Client-" + i).start();
}
}
}