Javaアプリケーション開発において、複数のタスクを同時に実行する並行処理は、システムのパフォーマンス向上、応答性の改善、およびリソースの効率的な活用に不可欠な技術です。本記事では、Javaで並行処理を実現するための基本的な概念から、スレッドの生成、同期メカニズム、そしてスレッドプールの利用方法までを解説します。
スレッドの作成方法
Javaで新しい実行スレッドを生成するには、主に二つのアプローチがあります。一つはjava.lang.Threadクラスを継承する方法、もう一つはjava.lang.Runnableインターフェースを実装する方法です。一般的には、Javaの単一継承の制約を考慮し、他のクラスを継承する可能性を残すため、Runnableインターフェースを実装する方式が推奨されます。
package com.example.concurrency.basics;
/**
* Threadクラスを継承してタスクを定義する例
*/
class CustomThreadWorker extends Thread {
private String workerId;
public CustomThreadWorker(String id) {
this.workerId = id;
}
@Override
public void run() {
System.out.println("Thread ID: " + workerId + " - Running on: " + Thread.currentThread().getName());
}
}
/**
* Runnableインターフェースを実装してタスクを定義する例
*/
class CustomRunnableTask implements Runnable {
private String taskId;
public CustomRunnableTask(String id) {
this.taskId = id;
}
@Override
public void run() {
System.out.println("Task ID: " + taskId + " - Running on: " + Thread.currentThread().getName());
}
}
public class ThreadCreationDemo {
public static void main(String[] args) {
System.out.println("--- Threadクラスの継承による生成 ---");
CustomThreadWorker threadA = new CustomThreadWorker("Worker-A");
CustomThreadWorker threadB = new CustomThreadWorker("Worker-B");
threadA.start(); // スレッドの実行を開始
threadB.start();
System.out.println("\n--- Runnableインターフェースの実装による生成 ---");
Thread taskX = new Thread(new CustomRunnableTask("Task-X"));
Thread taskY = new Thread(new CustomRunnableTask("Task-Y"));
taskX.start(); // スレッドの実行を開始
taskY.start();
}
}
スレッド同期と排他制御
複数のスレッドが共有リソースに同時にアクセスする際、データの破損や不整合(競合状態)を防ぐために同期メカニズムが必要となります。Javaでは、synchronizedキーワードを用いて排他制御を行うことができます。メソッドやコードブロックにsynchronizedを付与することで、一度に一つのスレッドのみがその領域にアクセスすることを保証し、データの一貫性を保ちます。
package com.example.concurrency.sync;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
/**
* 複数のスレッドから安全に値を増減させるカウンタクラス
*/
class SharedAtomicCounter {
private int value = 0; // 共有されるカウンタ
// synchronizedキーワードでメソッドを同期化
public synchronized void increment() {
value++;
}
public int getValue() {
return value;
}
}
public class SynchronizationDemo {
private static final int THREAD_COUNT = 10;
private static final int INCREMENTS_PER_THREAD = 1000;
public static void main(String[] args) throws InterruptedException {
SharedAtomicCounter counter = new SharedAtomicCounter();
ExecutorService executor = Executors.newFixedThreadPool(THREAD_COUNT);
for (int i = 0; i < THREAD_COUNT; i++) {
executor.submit(() -> {
for (int j = 0; j < INCREMENTS_PER_THREAD; j++) {
counter.increment();
}
});
}
executor.shutdown(); // 新しいタスクの受け付けを停止
// 全てのタスクが完了するまで最大1分間待機
if (!executor.awaitTermination(1, TimeUnit.MINUTES)) {
System.err.println("一部のタスクがタイムアウトしました。");
}
System.out.println("最終的なカウンタ値: " + counter.getValue()); // 期待値: THREAD_COUNT * INCREMENTS_PER_THREAD
}
}
スレッドプールの活用
スレッドの生成と破棄はシステムリソースを消費する操作です。スレッドプールを利用することで、これらのオーバーヘッドを削減し、スレッドの再利用、管理、およびタスクのスケジューリングを効率的に行うことができます。Javaでは、java.util.concurrent.ExecutorServiceインターフェースとその実装がスレッドプール機能を提供します。
package com.example.concurrency.pool;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
/**
* 実行するシンプルなタスク
*/
class SimplePrintTask implements Runnable {
private final String taskName;
public SimplePrintTask(String name) {
this.taskName = name;
}
@Override
public void run() {
System.out.println("タスク「" + taskName + "」を実行中 (スレッド: " + Thread.currentThread().getName() + ")");
try {
TimeUnit.MILLISECONDS.sleep(50); // タスクの処理をシミュレート
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
System.err.println("タスク「" + taskName + "」が中断されました。");
}
}
}
public class ThreadPoolDemo {
private static final int POOL_SIZE = 3; // スレッドプールのサイズ
private static final int NUMBER_OF_TASKS = 10; // 実行するタスク数
public static void main(String[] args) throws InterruptedException {
// 固定サイズのスレッドプールを作成
ExecutorService executor = Executors.newFixedThreadPool(POOL_SIZE);
System.out.println("スレッドプールにタスクを投入中...");
for (int i = 1; i <= NUMBER_OF_TASKS; i++) {
executor.submit(new SimplePrintTask("Task-" + i));
}
executor.shutdown(); // 新しいタスクの受け付けを停止
// 全てのタスクが完了するまで待機 (最大5秒)
System.out.println("\n全てのタスクの完了を待機中...");
if (executor.awaitTermination(5, TimeUnit.SECONDS)) {
System.out.println("全てのスレッドプールタスクが正常に完了しました。");
} else {
System.err.println("一部のタスクが時間内に完了しませんでした。");
}
}
}