Java のマルチスレッド・プログラミングの基本について
このページでは、Java のマルチスレッド・プログラミングについて基本的な概念から実装方法、注意点までを詳しく解説します。以下のリンクから各セクションにジャンプできます。
スレッドの基本
スレッドは、プロセス内で独立して実行可能な処理の単位です。Java では java.lang.Thread
クラスや java.lang.Runnable
インターフェースを使用してスレッドを扱います。
マルチスレッド・プログラミングの利点:
- 同時並行処理が可能になり、パフォーマンスが向上する。
- 複数タスクを同時に実行できる。
- 応答性が向上し、UIプログラムでのユーザー体験が良くなる。
注意点として、スレッドの競合やデッドロックなどの問題が発生する可能性があります。
スレッドの作成方法
Java でスレッドを作成する主な方法は以下の2つです:
Thread
クラスを拡張するRunnable
インターフェースを実装する
例: Thread クラスを拡張
class MyThread extends Thread {
public void run() {
System.out.println("Thread is running...");
}
}
public class Main {
public static void main(String[] args) {
MyThread thread = new MyThread();
thread.start(); // スレッドを開始
}
}
例: Runnable インターフェースを実装
class MyRunnable implements Runnable {
public void run() {
System.out.println("Runnable thread is running...");
}
}
public class Main {
public static void main(String[] args) {
Thread thread = new Thread(new MyRunnable());
thread.start(); // スレッドを開始
}
}
Runnable
を使用すると、スレッドの動作をカスタムクラスに継承させずに実装できるため、より柔軟です。
スレッドの同期
複数のスレッドが同時に共有リソースにアクセスする場合、データ競合が発生する可能性があります。この問題を解決するために、Java は同期機構を提供します。
同期の基本
synchronized
キーワードを使用して、特定のメソッドやブロックを同期化します。
例: メソッドの同期
class Counter {
private int count = 0;
public synchronized void increment() {
count++;
}
public synchronized int getCount() {
return count;
}
}
public class Main {
public static void main(String[] args) {
Counter counter = new Counter();
Thread t1 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
counter.increment();
}
});
Thread t2 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
counter.increment();
}
});
t1.start();
t2.start();
try {
t1.join();
t2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Final Count: " + counter.getCount());
}
}
スレッド間の通信
スレッド間の通信には、wait
, notify
, notifyAll
メソッドを使用します。
例: 生産者-消費者問題
import java.util.LinkedList;
class SharedQueue {
private final LinkedList queue = new LinkedList<>();
private final int capacity = 5;
public synchronized void produce(int value) throws InterruptedException {
while (queue.size() == capacity) {
wait(); // キューが満杯の間待機
}
queue.add(value);
System.out.println("Produced: " + value);
notify(); // 消費者スレッドに通知
}
public synchronized int consume() throws InterruptedException {
while (queue.isEmpty()) {
wait(); // キューが空の間待機
}
int value = queue.removeFirst();
System.out.println("Consumed: " + value);
notify(); // 生産者スレッドに通知
return value;
}
}
public class Main {
public static void main(String[] args) {
SharedQueue sharedQueue = new SharedQueue();
Thread producer = new Thread(() -> {
try {
for (int i = 0; i < 10; i++) {
sharedQueue.produce(i);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
});
Thread consumer = new Thread(() -> {
try {
for (int i = 0; i < 10; i++) {
sharedQueue.consume();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
});
producer.start();
consumer.start();
}
}
スレッドセーフティ
スレッドセーフティとは、複数のスレッドが同じリソースにアクセスする際にデータの整合性を保つことです。
- 不変クラスを使用する
volatile
キーワードを使用する- 同期コレクション(例:
ConcurrentHashMap
)を利用する
スレッドプール
スレッドプールは、タスクを効率的に実行するためにスレッドのセットを再利用する仕組みです。
例: ExecutorService を使用したスレッドプール
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Main {
public static void main(String[] args) {
ExecutorService executor = Executors.newFixedThreadPool(3);
for (int i = 0; i < 10; i++) {
final int task = i;
executor.execute(() -> {
System.out.println("Task " + task + " is running by " + Thread.currentThread().getName());
});
}
executor.shutdown();
}
}