こんにちは!バックエンドエンジニアのキョンです。今回は、並行処理の要となる「スレッド管理」について、実務での経験を基に詳しく解説していきます。
目次
1. Javaのスレッドとは
スレッドの基本概念
入社したての頃、私も「スレッドって結局なに?」って悩んでました。 簡単に言うと、スレッドは「プログラムの実行単位」です。複数のタスクを同時に処理したい時に使います。
スレッドの状態遷移
public class ThreadStateDemo {
public static void main(String[] args) {
Thread thread = new Thread(() -> {
System.out.println("スレッド実行中");
});
// NEW状態
System.out.println(thread.getState()); // NEW
thread.start(); // RUNNABLE状態へ
try {
thread.join(); // スレッドの終了を待機
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
// TERMINATED状態
System.out.println(thread.getState()); // TERMINATED
}
}
2. スレッドの基本操作
スレッドの作成方法
// 方法1: Threadクラスを継承
public class MyThread extends Thread {
@Override
public void run() {
System.out.println("スレッド実行中: " + Thread.currentThread().getName());
}
}
// 方法2: Runnableインターフェースを実装
public class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("Runnable実行中: " + Thread.currentThread().getName());
}
}
// 使用例
MyThread thread1 = new MyThread();
thread1.start();
Thread thread2 = new Thread(new MyRunnable());
thread2.start();
// 方法3: ラムダ式を使用(最近はこちらが主流)
Thread thread3 = new Thread(() -> {
System.out.println("ラムダ式で実行中");
});
thread3.start();
3. 同期処理と排他制御
synchronized の使い方
実際のプロジェクトでよく使用するパターンです:
public class BankAccount {
private int balance;
// メソッドレベルの同期
public synchronized void deposit(int amount) {
balance += amount;
}
// ブロックレベルの同期
public void withdraw(int amount) {
synchronized(this) {
if (balance >= amount) {
balance -= amount;
}
}
}
}
ロック機構の活用
public class OrderProcessor {
private final ReentrantLock lock = new ReentrantLock();
private final Condition condition = lock.newCondition();
public void processOrder(Order order) {
lock.lock();
try {
// 注文処理
while (!canProcess(order)) {
condition.await();
}
// 実際の処理
process(order);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
lock.unlock();
}
}
}
4. スレッドプールの実践的な使い方
ExecutorServiceの活用
Web系のバッチ処理でよく使用する例です:
public class BatchProcessor {
private final ExecutorService executor =
Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
public void processBatch(List<Task> tasks) {
List<Future<?>> futures = new ArrayList<>();
for (Task task : tasks) {
Future<?> future = executor.submit(() -> {
try {
task.process();
} catch (Exception e) {
logger.error("タスク処理エラー: ", e);
}
});
futures.add(future);
}
// 全タスクの完了を待機
for (Future<?> future : futures) {
try {
future.get();
} catch (Exception e) {
logger.error("タスク完了待機エラー: ", e);
}
}
}
}
CompletableFutureの活用
非同期処理の組み合わせが必要な場合の例:
public class AsyncProcessor {
public CompletableFuture<OrderResult> processOrderAsync(Order order) {
return CompletableFuture.supplyAsync(() -> {
// 在庫チェック
return checkInventory(order);
}).thenApplyAsync(inventory -> {
// 決済処理
return processPayment(order, inventory);
}).thenApplyAsync(payment -> {
// 配送手配
return arrangeShipping(order, payment);
}).exceptionally(throwable -> {
logger.error("処理エラー: ", throwable);
return new OrderResult(OrderStatus.ERROR);
});
}
}
5. よくあるトラブルと対策
デッドロックの防止
public class DeadlockPrevention {
private final Object lock1 = new Object();
private final Object lock2 = new Object();
// 悪い例:デッドロックの可能性あり
public void badMethod() {
synchronized(lock1) {
synchronized(lock2) {
// 処理
}
}
}
// 良い例:ロックの順序を固定
public void goodMethod() {
Object firstLock = lock1.hashCode() < lock2.hashCode() ? lock1 : lock2;
Object secondLock = lock1.hashCode() < lock2.hashCode() ? lock2 : lock1;
synchronized(firstLock) {
synchronized(secondLock) {
// 処理
}
}
}
}
メモリ可視性の問題
public class VisibilityExample {
// volatileで可視性を保証
private volatile boolean flag = false;
public void doWork() {
while (!flag) {
// 何らかの処理
}
}
public void stopWork() {
flag = true;
}
}
まとめ
スレッド管理は難しく感じるかもしれませんが、基本パターンを押さえれば怖くありません。
私の経験から、以下のポイントを意識すると良いでしょう:
- 適切な同期機構の選択
- スレッドプールの活用
- デッドロックの防止
- メモリ可視性への配慮
さらなる学習のために
- Java Concurrency in Practice
- Java SE Concurrency
- Effective Java 第3版
次回は「Java メモリモデルについて」解説する予定です。お楽しみに!
コメント