Javaスレッドの状態管理と効率的な並行処理手法

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();
        }
    }
}

タグ: Java 多線程 スレッドプール 原子性 cas

9月17日 18:22 投稿