スレッドの生成とライフサイクル
Javaにおけるスレッドは、複数の方法で生成可能です。それぞれの方法には特徴があり、用途に応じて使い分ける必要があります。
- Threadクラスを継承:run()メソッドをオーバーライドしてスレッド動作を定義します。
class CustomThread extends Thread {
public void run() {
// スレッド実行内容
}
}
// スレッド開始
CustomThread thread = new CustomThread();
thread.start();
- Runnableインターフェースを実装:run()メソッドを提供し、Threadクラスと分離できます。
class Task implements Runnable {
public void run() {
// タスク処理
}
}
// スレッド作成と実行
Task task = new Task();
Thread thread = new Thread(task);
thread.start();
- Callableインターフェースを使用:戻り値を持つタスクを定義できます。
class ResultTask implements Callable<String> {
public String call() {
return "完了";
}
}
ResultTask task = new ResultTask();
FutureTask<String> future = new FutureTask<>(task);
Thread thread = new Thread(future);
thread.start();
- スレッドプール経由:ExecutorServiceでスレッド管理を効率化します。
ExecutorService pool = Executors.newFixedThreadPool(3);
pool.submit(() -> {
// タスク内容
});
pool.shutdown();
スレッドの状態遷移:
- 新規作成(NEW)
- 実行可能(RUNNABLE)
- 実行中(RUNNING)
- 待機中(WAITING/TIMED_WAITING)
- ブロック中(BLOCKED)
- 終了(TERMINATED)
synchronizedの内部メカニズム
排他制御にはsynchronizedキーワードが使用され、JVMレベルのロック機構に基づいています。
- 利点:ロックの段階的昇格により、競合が少ない場合のパフォーマンスを最適化します。
- 欠点:ロック粒度が広いと競合が発生しやすくなります。
ロックの昇格プロセス:
- 無ロック状態
- 偏向ロック(一度しかアクセスがない場合)
- 軽量ロック(複数スレッドによる競合)
- 重量ロック(スレッドが多く競合する場合)
ダブルチェックシングルトンの実装
シングルトンインスタンスの初期化には、スレッドセーフな方法が必要です。
public class Singleton {
private static volatile Singleton uniqueInstance;
private Singleton() {}
public static Singleton getInstance() {
if (uniqueInstance == null) {
synchronized (Singleton.class) {
if (uniqueInstance == null) {
uniqueInstance = new Singleton();
}
}
}
return uniqueInstance;
}
}
volatileの必要性:インスタンス生成時の命令の再順序化を防ぎ、完全に初期化されたオブジェクトを確実に返します。
AbstractQueuedSynchronizer(AQS)の仕組み
AQSは、Javaの同期処理の基盤となるフレームワークです。ReentrantLockやCountDownLatchなどの基底クラスとして利用されます。
- state変数:現在のロックの取得状況を示します。
- Node双方向キュー:ロックを待つスレッドを管理します。
ReentrantLockでの使用例:
ReentrantLock lock = new ReentrantLock();
new Thread(() -> {
lock.lock();
try {
// 排他処理
} finally {
lock.unlock();
}
}).start();
スレッドプールの活用方法
スレッドプールの主な種類:
- 固定サイズスレッドプール:Executors.newFixedThreadPool()
- キャッシュ可能スレッドプール:Executors.newCachedThreadPool()
- 単一スレッドプール:Executors.newSingleThreadExecutor()
- スケジュール付きスレッドプール:Executors.newScheduledThreadPool()
Fork/Joinフレームワークの活用
大規模タスクを分割して並列実行するフレームワークです。
class SumTask extends RecursiveTask<Integer> {
private final int[] array;
private final int start;
private final int end;
public SumTask(int[] array, int start, int end) {
this.array = array;
this.start = start;
this.end = end;
}
protected Integer compute() {
if (end - start <= 10) {
return Arrays.stream(array, start, end).sum();
} else {
int mid = (start + end) / 2;
SumTask left = new SumTask(array, start, mid);
SumTask right = new SumTask(array, mid, end);
left.fork();
right.fork();
return left.join() + right.join();
}
}
}