MENU

Javaのスレッド管理を完全理解!現役エンジニアが解説

こんにちは!バックエンドエンジニアのキョンです。今回は、並行処理の要となる「スレッド管理」について、実務での経験を基に詳しく解説していきます。

目次

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

まとめ

スレッド管理は難しく感じるかもしれませんが、基本パターンを押さえれば怖くありません。

私の経験から、以下のポイントを意識すると良いでしょう:

  1. 適切な同期機構の選択
  2. スレッドプールの活用
  3. デッドロックの防止
  4. メモリ可視性への配慮

さらなる学習のために

  • Java Concurrency in Practice
  • Java SE Concurrency
  • Effective Java 第3版

次回は「Java メモリモデルについて」解説する予定です。お楽しみに!

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

コメント

コメントする

目次