スレッド基礎
スレッドライフサイクル
flowchart TB
n((New)) ==> |start()|R{{Runnable}}
R <--> |スケジューラ / yield|Ring{{Running}}
Ring ==> |run() 完了|d((Dead))
Ring ==> |ブロッキングイベント: IO, join, sleep...| Bl{{Blocked}}
Bl ==> |ブロック解除| R
Ring ==> |ロックを持つスレッドが wait() を呼び出す|WP{{Wait Pool}}
WP ==> |notify / notifyAll / interrupt| LP{{Lock Pool}}
LP ==>|ロック取得| R
スレッドの作成方法
Thread クラスを継承
ExtendsThread.java
class MyThread extends Thread {
@Override
public void run() {
System.out.println("Thread extends Thread - run()");
}
}
public static void main(String[] args) {
MyThread t = new MyThread();
t.start();
}Runnable インターフェースを実装
ImplementsRunnable.java
class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("Thread implements Runnable - run()");
}
}
public static void main(String[] args) {
Thread t = new Thread(new MyRunnable(), "t1");
t.start();
}Callable インターフェースを実装
- 戻り値あり
- 例外をスローできる
ImplementsCallable.java
class MyCallable implements Callable<String> {
@Override
public String call() throws Exception {
System.out.println("Thread implements Callable - call()");
return "Hello Callable";
}
}
public static void main(String[] args) throws Exception {
FutureTask<String> futureTask = new FutureTask<>(new MyCallable());
Thread t = new Thread(futureTask, "t1");
t.start();
System.out.println("戻り値: " + futureTask.get());
}スレッドプールを使用
UseThreadPool.java
ExecutorService executor = Executors.newFixedThreadPool(5);
executor.submit(() -> {
System.out.println("Task running in thread pool");
});
executor.shutdown();Runnable vs Callable
| 特性 | Runnable | Callable |
|---|---|---|
| メソッド | run() | call() |
| 戻り値 | void | 総称型 V |
| 例外処理 | checked exception をスローできない | Exception をスローできる |
| JDK バージョン | 1.0 | 1.5 |
classDiagram
class FutureTask{
Callable
}
class Callable
<<interface>> Callable
class Runnable
<<interface>> Runnable
Callable --o FutureTask
Runnable <|.. FutureTask
デーモンスレッド (Daemon Thread)
デーモンスレッドは他のスレッドにサービスを提供する特殊なスレッドで、バックグラウンドでシステムサービス(GC など)を実行する。
システムにデーモンスレッドしか残っていない場合、JVM は自動的に終了する。
DaemonDemo.java
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
System.out.println(Thread.currentThread().getName() + " working, " +
(Thread.currentThread().isDaemon() ? "Daemon Thread" : "User Thread"));
while (true) {}
}, "t1");
t1.setDaemon(true); // デーモンスレッドに設定
t1.start();
Thread.sleep(Duration.ofSeconds(3));
System.out.println(Thread.currentThread().getName() + " --- main thread ends");
}
// Output:
// t1 working, Daemon Thread
// main --- main thread ends
// Process finished with exit code 0UserThreadDemo.java
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
System.out.println(Thread.currentThread().getName() + " working, " +
(Thread.currentThread().isDaemon() ? "Daemon Thread" : "User Thread"));
while (true) {}
}, "t1");
t1.start(); // デフォルトは User Thread
Thread.sleep(Duration.ofSeconds(3));
System.out.println(Thread.currentThread().getName() + " --- main thread ends");
}
// Output:
// t1 working, User Thread
// main --- main thread ends
// (t1 がまだ実行中のため、プログラムは終了しない)ThreadLocal
ThreadLocal は各スレッドに独立した変数のコピーを提供し、共有状態の問題を回避する。
| ビジネスシーン | 関連フレームワーク |
|---|---|
| Web アプリのセッション管理 | Spring Session, Servlet HttpSession |
| ログトレース | Logback, Slf4j, Log4j (MDC) |
| DB 接続/トランザクション管理 | Spring Data, Hibernate, MyBatis |
| スレッドプールでのコンテキスト伝達 | Spring Framework |
スレッドプールを使用する際は、タスク終了時に ThreadLocal を必ずクリアし、メモリリークを回避すること。
スレッド間通信
wait / sleep の違い
| 特性 | wait() | sleep() |
|---|---|---|
| 所属クラス | Object | Thread |
| ロック解放 | する | しない |
| 使用条件 | synchronized ブロック内のみ | どこでも |
| 起床方法 | notify() / notifyAll() | 時間経過で自動起床 |
Spurious Wakeup(偽の起床):
wait() は if ではなく while ループと一緒に使用し、偽の起床を回避すること。wait / notify 例
WaitNotifyDemo.java
synchronized (lock) {
while (!condition) { // while を使用して偽の起床を回避
lock.wait();
}
// 処理ロジック
}
// 別のスレッド
synchronized (lock) {
condition = true;
lock.notifyAll();
}