Javaにおけるマルチスレッディングと同期制御、スレッドプールの実践

マルチスレッドの実装方法

Javaでは複数のスレッドを生成して並列処理を行うことができます。主な実装パターンは以下の3つです。

1. Threadクラスの継承

カスタムスレッドクラスを作成し、Threadクラスを拡張する方法です。最もシンプルなアプローチです。

class WorkerThread extends Thread {
    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println("作業スレッド: " + i);
            try {
                Thread.sleep(100); // 一時停止で動作確認
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }
    }
}

public class ThreadExample1 {
    public static void main(String[] args) {
        WorkerThread worker = new WorkerThread();
        worker.start(); // スタートでrunが非同期に実行

        for (int i = 0; i < 5; i++) {
            System.out.println("メインスレッド: " + i);
        }
    }
}

2. Runnableインタフェースの実装

Runnableを実装することで、タスクの内容を定義します。この方法は継承を使わず柔軟性が高いです。

public class ThreadExample2 {
    public static void main(String[] args) {
        Runnable task = () -> {
            for (int i = 0; i < 5; i++) {
                System.out.println("タスク実行中: " + i);
            }
        };

        Thread thread = new Thread(task);
        thread.start();

        for (int i = 0; i < 5; i++) {
            System.out.println("メイン: " + i);
        }
    }
}

3. CallableとFutureによる結果取得

Callableを使用すると、スレッドの処理結果を後から取得できます。Futureオブジェクトで結果を待機・取得可能です。

import java.util.concurrent.*;

class SumTask implements Callable<Integer> {
    private final int limit;

    SumTask(int limit) {
        this.limit = limit;
    }

    @Override
    public Integer call() {
        return IntStream.rangeClosed(1, limit).sum();
    }
}

public class ThreadExample3 {
    public static void main(String[] args) {
        ExecutorService executor = Executors.newFixedThreadPool(2);
        Future<Integer> result1 = executor.submit(new SumTask(100));
        Future<Integer> result2 = executor.submit(new SumTask(50));

        try {
            System.out.println("合計1: " + result1.get());
            System.out.println("合計2: " + result2.get());
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
        } finally {
            executor.shutdown();
        }
    }
}

スレッド間のデータ競合対策

複数スレッドが共通リソースにアクセスする際には、排他制御が必要です。代表的な手法を紹介します。

同期コードブロック(synchronizedブロック)

特定のオブジェクトに対してロックをかけ、同時アクセスを防ぎます。

public class BankAccount {
    private double balance = 1000.0;
    private final Object lock = new Object();

    public void withdraw(double amount) {
        String name = Thread.currentThread().getName();
        synchronized (lock) {
            if (balance >= amount) {
                System.out.println(name + "が" + amount + "円を引き出しました");
                balance -= amount;
                System.out.println("残高: " + balance + "円");
            } else {
                System.out.println(name + "の引き出し失敗。残高不足");
            }
        }
    }
}

同期メソッド(synchronizedメソッド)

メソッド全体をロック対象とする簡易的な方法です。

public synchronized void withdraw(double amount) {
    // 同期処理内容
}

Lockインタフェースの利用

より細かい制御が可能なReentrantLockを使う方法です。明示的にロックとアンロックを管理します。

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class BankAccountWithLock {
    private double balance = 1000.0;
    private final Lock lock = new ReentrantLock();

    public void withdraw(double amount) {
        String name = Thread.currentThread().getName();
        lock.lock();
        try {
            if (balance >= amount) {
                System.out.println(name + "が" + amount + "円を引き出しました");
                balance -= amount;
                System.out.println("残高: " + balance + "円");
            } else {
                System.out.println(name + "の引き出し失敗。残高不足");
            }
        } finally {
            lock.unlock();
        }
    }
}

スレッドプールの活用

頻繁にスレッドを作成・破棄するのはコストが高いため、再利用可能なスレッドプールを使用します。

ExecutorService service = Executors.newFixedThreadPool(4);

// タスクの投入
for (int i = 0; i < 8; i++) {
    final int taskId = i;
    service.submit(() -> {
        System.out.println("タスク" + taskId + " 実行スレッド: " +
                Thread.currentThread().getName());
    });
}

// 使用終了時にシャットダウン
service.shutdown();

並列処理と同時処理の違い

  • 並列処理(Parallelism): 複数のCPUコアを使って本当に同時に処理を行う
  • 同時処理(Concurrency): 単一または複数のコア上で、複数のタスクを切り替えながら進行させる

JavaのスレッドはOSレベルのスレッドとして扱われ、マルチコア環境では真の並列実行が可能です。

タグ: Java マルチスレッディング スレッド同期 スレッドプール 並列処理

5月31日 08:34 投稿