Java並行処理面接ガイド:スレッドの基礎、安全性、Executorフレームワーク

スレッドの基本概念

プロセスとスレッドの違い

プロセスはOSがメモリ、ファイルハンドル、ネットワークソケットなどのリソースを割り当てる独立した実行単位である。一方、スレッドはプロセス内部で動作する軽量な実行単位で、同一プロセスのスレッドはヒープやメタスペースなどを共有する。

並行と並列の違い

  • 並行(Concurrent):単一コアでタイムスライスを使って複数タスクを高速に切り替える。人間には同時に見えるが、実はシリアル実行。
  • 並列(Parallel):マルチコアで複数タスクを物理的に同時実行。

スレッドの生成パターン

1. Threadクラスを継承


class CounterTask extends Thread {
    private final int id;
    CounterTask(int id) { this.id = ids; }

    @Override
    public void run() {
        System.out.println("Task-" + id + " started");
    }

    public static void main(String[] args) {
        new CounterTask(1).start();
        new CounterTask(2).start();
    }
}

2. Runnable実装


class LogWorker implements Runnable {
    public void run() {
        System.out.println("Logging from " + Thread.currentThread().getName());
    }

    public static void main(String[] args) {
        Thread t1 = new Thread(new LogWorker(), "worker-A");
        Thread t2 = new Thread(new LogWorker(), "worker-B");
        t1.start();
    }
}

3. Callable + FutureTask


class SumTask implements Callable<Integer> {
    private final int n;
    SumTask(int upper) { this.n = upper; }

    public Integer call() {
        return IntStream.rangeClosed(1, n).sum();
    }

    public static void main(String[] args) throws Exception {
        FutureTask<Integer> ft = new FutureTask<>(new SumTask(100));
        new Thread(ft).start();
        System.out.println("Result = " + ft.get());
    }
}

4. ExecutorServiceによる生成


class PoolDemo {
    public static void main(String[] args) {
        ExecutorService pool = Executors.newCachedThreadPool();
        pool.execute(() -> System.out.println("Running in pool"));
        pool.shutdown();
    }
}

Runnable vs Callable

観点RunnableCallable
戻り値voidGenerics指定可能
例外内部で処理throws Exception可
組み合わせThread直接FutureTask経由

Thread#start と直接の run 呼び出し

  • start():新規スタックフレームを生成しOSスケジューラに登録。
  • run():現在のスレッドで普通のメソッド呼び出し。

ライフサイクルステート

NEW → start() → RUNNABLE → CPU取得 → RUNNING → 完了 → TERMINATED
                               ↓
                        BLOCKED, WAITING, TIMED_WAITING

join で順序制御


public class OrderedThreads {
    public static void main(String[] args) throws InterruptedException {
        Thread first = new Thread(() -> System.out.println("Alpha"));
        Thread second = new Thread(() -> {
            try { first.join(); } catch (InterruptedException ignore) {}
            System.out.println("Beta");
        });
        Thread third = new Thread(() -> {
            try { second.join(); } catch (InterruptedException ignore) {}
            System.out.println("Gamma");
        });
        third.start(); second.start(); first.start();
    }
}

wait/notify モデル


class Signal {
    private static final Object lock = new Object();
    private static boolean ready = false;

    public static void main(String[] args) {
        Thread waiter = new Thread(() -> {
            synchronized (lock) {
                while (!ready) {
                    try { lock.wait(); } catch (InterruptedException ignored) {}
                }
                System.out.println("Received signal");
            }
        });

        Thread notifier = new Thread(() -> {
            synchronized (lock) {
                ready = true;
                lock.notifyAll();
            }
        });

        waiter.start();
        notifier.start();
    }
}

wait と sleep の違い

  • 所属:wait は Object のインスタンスメソッド、sleep は Thread の static メソッド。
  • ロック:wait はモニタロックを解放、sleep は保持したまま。
  • 起床条件:wait は notify/notifyAll またはタイムアウト、sleep は必ず時間経過。

スレッドセーフティ基礎

競合状態(Race Condition)

複数スレッドが共有変数に同時にアクセスし、結果が最終的な実行順序に依存する問題。

メモリ可視性(Visibility)

CPUキャッシュやコンパイラの最適化により、あるスレッドが書き込んだ値が他スレッドに即座に見えない問題。

同期プリミティブ

  • synchronized:モニタロックによる相互排除
  • volatile:可視性の保証と先行発生関係
  • java.util.concurrent.locks:より柔軟なロック機構
  • Atomic*クラス:CAS(Compare-And-Swap)によるロックフリー更新

Executorフレームワーク


class FixedPoolExample {
    public static void main(String[] args) {
        ExecutorService exec = Executors.newFixedThreadPool(4);
        IntStream.range(0, 10)
                 .forEach(i -> exec.submit(() -> System.out.println("Task " + i)));
        exec.shutdown();
    }
}

カスタムThreadFactory


ThreadFactory factory = r -> {
    Thread t = new Thread(r);
    t.setDaemon(false);
    t.setName("biz-thread-" + System.nanoTime());
    return t;
};
ExecutorService es = Executors.newFixedThreadPool(4, factory);

まとめ

Java の並行 API はスレッド生成から高度な Executor フレームワークまで幅広く揃っており、適切な同期戦略を選ぶことでスケーラブルで安全なアプリケーションを構築できる。

タグ: Java concurrency Thread ExecutorService synchronization

7月19日 20:35 投稿