Java - スレッドプール
こんにちは、将来的なJava開発者の皆さん!今日は、スレッドプールのワクワクする世界に飛び込んでいきましょう。プログラミングに初めての方でも心配しないでください。私は年間にわたって数えきれないほどの生徒に教えたことがありますが、一歩一歩この概念を説明していきます。だから、コーヒー(またはお好みでお茶でもいいですよ)を片手に、始めましょう!
スレッドプールとは?
あなたが賑やかなレストランを運営しているとしましょう。新しいお客様が来るたびに、彼らをサービスするために新しいウェイターを採用するというわけにはいきません。それではめちゃくちゃなり、高いコストがかかります!代わりに、一定の数のウェイターがいて、複数のお客様をサービスするのです。それが基本的にJavaプログラミングのスレッドプールが行うことです。
スレッドプールは、事前に生成され、再利用可能なスレッドのグループであり、タスクを実行するために利用されます。新しいスレッドを各タスクごとに作成するのではなく、既存のスレッドのプールを使用してこれらのタスクを実行します。
Javaでなぜスレッドプールを使用するのか?
「なぜスレッドプールを気にするの?新しいスレッドを必要な分作ればいいじゃない?」と思うかもしれませんが、私の教育経験からのちょっとした話で説明しましょう。
かつて、私の生徒の一人がアプリケーション内の各タスクごとに新しいスレッドを作ろうとしました。彼のプログラムは小さな入力ではうまく動きましたが、大きなデータセットを処理しようとしたとき、彼のコンピュータがほぼクラッシュしました!そんなとき、私は彼にスレッドプールを紹介しました。
以下は、スレッドプールを使用する理由のいくつかです:
- パフォーマンス向上:スレッドの作成と破棄は高価です。スレッドプールはスレッドを再利用することで、オーバーヘッドを節約します。
- リソース管理:同時に実行できるスレッドの最大数を制御できます。
- 応答性向上:タスクはすでに実行中のスレッドによって今すぐ実行できます。
Javaでスレッドプールを作成する
Javaは、Executors
クラスを通じて、複数の方法でスレッドプールを作成することができます。以下に3つの一般的なメソッドを見ていきましょう:
1. newFixedThreadPool() メソッドを使用してスレッドプールを作成する
このメソッドは、固定の数のスレッドで構成されたスレッドプールを作成します。以下に例を示します:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class FixedThreadPoolExample {
public static void main(String[] args) {
ExecutorService executor = Executors.newFixedThreadPool(5);
for (int i = 0; i < 10; i++) {
Runnable worker = new WorkerThread("" + i);
executor.execute(worker);
}
executor.shutdown();
while (!executor.isTerminated()) {
}
System.out.println("全てのスレッドを完了しました");
}
}
class WorkerThread implements Runnable {
private String message;
public WorkerThread(String s) {
this.message = s;
}
public void run() {
System.out.println(Thread.currentThread().getName() + " (開始) メッセージ = " + message);
processMessage();
System.out.println(Thread.currentThread().getName() + " (終了)");
}
private void processMessage() {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
この例では、5つのスレッドで構成された固定スレッドプールを作成し、10個のタスクをこのプールに提出します。プールは、利用可能になった場合にスレッドを再利用して、これらの10個のタスクを実行します。
2. newCachedThreadPool() メソッドを使用してスレッドプールを作成する
このメソッドは、必要に応じて新しいスレッドを作成しますが、可能な場合は既存のスレッドを再利用します。以下にその仕組みを見ていきましょう:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class CachedThreadPoolExample {
public static void main(String[] args) {
ExecutorService executor = Executors.newCachedThreadPool();
for (int i = 0; i < 100; i++) {
executor.execute(new Task());
}
executor.shutdown();
}
}
class Task implements Runnable {
public void run() {
System.out.println("スレッド名: " + Thread.currentThread().getName());
}
}
この例では、キャッシュされたスレッドプールを作成し、100個のタスクを提出します。プールは、必要に応じて新しいスレッドを作成し、可能な場合は再利用します。
3. newScheduledThreadPool() メソッドを使用してスレッドプールを作成する
このメソッドは、指定された遅延後にコマンドを実行するか、定期的に実行するスレッドプールを作成します。以下に例を示します:
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class ScheduledThreadPoolExample {
public static void main(String[] args) {
ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);
Runnable task = () -> System.out.println("タスクを実行中: " + System.nanoTime());
executor.scheduleAtFixedRate(task, 0, 2, TimeUnit.SECONDS);
}
}
この例では、1つのスレッドで構成されたスケジュールされたスレッドプールを作成し、2秒ごとにタスクを実行します。
ExecutorServiceのメソッド
以下は、ExecutorService
インターフェースの重要なメソッドの一覧です:
メソッド | 説明 |
---|---|
execute(Runnable) |
将来的に指定されたコマンドを実行します |
submit(Callable) |
実行結果を返すタスクを実行し、Future を返します |
shutdown() |
前に提出されたタスクを実行し、新しいタスクを受け付けないように、順次シャットダウンを開始します |
shutdownNow() |
実行中のすべてのタスクを停止しようとし、待機中のタスクの処理を停止します |
isShutdown() |
この実行機がシャットダウンされたかどうかを返します |
isTerminated() |
シャットダウン後にすべてのタスクが完了したかどうかを返します |
結論
それでは、皆さん!Javaのスレッドプールを理解するために、なぜそれが役立つかから、さまざまな方法で作成して使う方法まで旅をしました。レストランのアナロジーで説明したように、スレッドプールはリソースを効率的に管理するのに役立ちます。
私の教育経験の中で、基本的なスレッドの概念に苦戦した生徒が、複雑で効率的なマルチスレッドアプリケーションを構築することができるようになることを沢山見てきました。実践と忍耐力を持つことで、あなたもそうなるでしょう!
Javaの旅を続ける中で、探求と実験を続けてください。スレッドプールは、Javaが並列プログラミングに提供する膨大なツールキットの中の一つのツールです。幸せなコーディングをし、あなたのスレッドが常に効率的にプールされることを願っています!
Credits: Image by storyset