マルチスレッドの実装方法
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レベルのスレッドとして扱われ、マルチコア環境では真の並列実行が可能です。