スレッド

Java – 複数のスレッドを同時に実行して終了を待機する

Javaでは、複数のスレッドを同時に実行し、それらの終了を待機するには、主にExecutorServiceCountDownLatchが利用されます。

ExecutorServiceを使用する場合、invokeAllメソッドでタスクを一括実行し、全タスクの完了を待機できます。

また、CountDownLatchを用いると、スレッドごとにカウントを減らし、全スレッドの終了を待つことが可能です。

これにより、非同期処理の管理が容易になります。

複数スレッドの同時実行の仕組み

Javaでは、スレッドを使用することで、複数の処理を同時に実行することができます。

スレッドは、プログラムの実行単位であり、各スレッドは独立して動作します。

これにより、CPUのリソースを効率的に利用し、プログラムのパフォーマンスを向上させることが可能です。

スレッドの基本

  • スレッド: プログラム内で独立して実行される処理の単位。
  • マルチスレッド: 複数のスレッドを同時に実行すること。
  • コンテキストスイッチ: CPUが実行中のスレッドを切り替える処理。

これにより、複数のスレッドが同時に実行されているように見えます。

スレッドの作成方法

Javaでは、スレッドを作成する方法が2つあります。

方法説明
Threadクラスの拡張Threadクラスを継承し、runメソッドをオーバーライドする。
Runnableインターフェースの実装Runnableインターフェースを実装し、runメソッドを定義する。

スレッドの実行

スレッドを実行するには、startメソッドを呼び出します。

これにより、スレッドが新しい実行の流れを開始します。

スレッドが実行を終えると、runメソッドが終了します。

以下は、スレッドを作成し、同時に実行する基本的なサンプルコードです。

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
class MyRunnable implements Runnable {
    @Override
    public void run() {
        // スレッドの処理内容
        for (int i = 0; i < 5; i++) {
            System.out.println(Thread.currentThread().getName() + " - カウント: " + i);
            try {
                Thread.sleep(100); // 100ミリ秒待機
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
public class App {
    public static void main(String[] args) {
        // スレッドプールを作成
        ExecutorService executor = Executors.newFixedThreadPool(3);
        
        // スレッドを実行
        for (int i = 0; i < 3; i++) {
            executor.execute(new MyRunnable());
        }
        
        // スレッドプールをシャットダウン
        executor.shutdown();
    }
}

このコードでは、3つのスレッドを同時に実行しています。

各スレッドは、カウントを0から4まで表示し、100ミリ秒待機します。

出力結果は以下のようになります。

Thread-0 - カウント: 0
Thread-1 - カウント: 0
Thread-2 - カウント: 0
Thread-0 - カウント: 1
Thread-1 - カウント: 1
Thread-2 - カウント: 1
Thread-0 - カウント: 2
Thread-1 - カウント: 2
Thread-2 - カウント: 2
Thread-0 - カウント: 3
Thread-1 - カウント: 3
Thread-2 - カウント: 3
Thread-0 - カウント: 4
Thread-1 - カウント: 4
Thread-2 - カウント: 4

このように、Javaではスレッドを利用することで、複数の処理を同時に実行することができます。

スレッドの管理には、ExecutorServiceを使用することで、より効率的にスレッドを扱うことが可能です。

複数スレッドの終了を待機する方法

複数のスレッドを同時に実行した後、それらのスレッドがすべて終了するのを待機することは、プログラムの正しい動作を保証するために重要です。

Javaでは、スレッドの終了を待機するために、いくつかの方法があります。

ここでは、主にThread.join()メソッドとExecutorServiceを使用した方法を紹介します。

Thread.join()メソッド

Thread.join()メソッドを使用すると、特定のスレッドが終了するまで、呼び出し元のスレッドをブロックすることができます。

これにより、スレッドの終了を待機することができます。

以下はその基本的な使い方です。

import java.util.ArrayList;
import java.util.List;
class MyRunnable implements Runnable {
    @Override
    public void run() {
        // スレッドの処理内容
        for (int i = 0; i < 5; i++) {
            System.out.println(Thread.currentThread().getName() + " - カウント: " + i);
            try {
                Thread.sleep(100); // 100ミリ秒待機
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
public class App {
    public static void main(String[] args) throws InterruptedException {
        List<Thread> threads = new ArrayList<>(); // スレッドを格納するリスト
        
        // スレッドを作成
        for (int i = 0; i < 3; i++) {
            Thread thread = new Thread(new MyRunnable());
            threads.add(thread); // スレッドをリストに追加
            thread.start(); // スレッドを開始
        }
        
        // 各スレッドの終了を待機
        for (Thread thread : threads) {
            thread.join(); // スレッドの終了を待機
        }
        
        System.out.println("すべてのスレッドが終了しました。");
    }
}

このコードでは、3つのスレッドを作成し、それぞれのスレッドがカウントを表示します。

join()メソッドを使用して、すべてのスレッドが終了するのを待機します。

出力結果は以下のようになります。

Thread-0 - カウント: 0
Thread-1 - カウント: 0
Thread-2 - カウント: 0
Thread-0 - カウント: 1
Thread-1 - カウント: 1
Thread-2 - カウント: 1
Thread-0 - カウント: 2
Thread-1 - カウント: 2
Thread-2 - カウント: 2
Thread-0 - カウント: 3
Thread-1 - カウント: 3
Thread-2 - カウント: 3
Thread-0 - カウント: 4
Thread-1 - カウント: 4
Thread-2 - カウント: 4
すべてのスレッドが終了しました。

ExecutorServiceを使用した方法

ExecutorServiceを使用する場合、shutdown()メソッドを呼び出すことで、すべてのスレッドの終了を待機することができます。

awaitTermination()メソッドを使用することで、指定した時間内にすべてのスレッドが終了するのを待つことができます。

以下はその例です。

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
class MyRunnable implements Runnable {
    @Override
    public void run() {
        // スレッドの処理内容
        for (int i = 0; i < 5; i++) {
            System.out.println(Thread.currentThread().getName() + " - カウント: " + i);
            try {
                Thread.sleep(100); // 100ミリ秒待機
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
public class App {
    public static void main(String[] args) throws InterruptedException {
        ExecutorService executor = Executors.newFixedThreadPool(3); // スレッドプールを作成
        
        // スレッドを実行
        for (int i = 0; i < 3; i++) {
            executor.execute(new MyRunnable());
        }
        
        executor.shutdown(); // スレッドプールをシャットダウン
        
        // すべてのスレッドの終了を待機
        if (executor.awaitTermination(1, TimeUnit.MINUTES)) {
            System.out.println("すべてのスレッドが終了しました。");
        } else {
            System.out.println("タイムアウトしました。");
        }
    }
}

このコードでは、ExecutorServiceを使用して3つのスレッドを実行し、shutdown()メソッドでスレッドプールをシャットダウンします。

その後、awaitTermination()メソッドを使用して、すべてのスレッドが終了するのを待機します。

出力結果は以下のようになります。

Thread-0 - カウント: 0
Thread-1 - カウント: 0
Thread-2 - カウント: 0
Thread-0 - カウント: 1
Thread-1 - カウント: 1
Thread-2 - カウント: 1
Thread-0 - カウント: 2
Thread-1 - カウント: 2
Thread-2 - カウント: 2
Thread-0 - カウント: 3
Thread-1 - カウント: 3
Thread-2 - カウント: 3
Thread-0 - カウント: 4
Thread-1 - カウント: 4
Thread-2 - カウント: 4
すべてのスレッドが終了しました。

このように、JavaではThread.join()メソッドやExecutorServiceを使用することで、複数のスレッドの終了を待機することができます。

これにより、プログラムの正しい動作を確保することができます。

実践例:複数スレッドの同時実行と終了待機

ここでは、Javaを使用して複数のスレッドを同時に実行し、それらのスレッドが終了するのを待機する実践的な例を示します。

この例では、複数のスレッドが異なるタスクを実行し、すべてのスレッドが完了するまでメインスレッドが待機します。

例の概要

このプログラムでは、3つのスレッドがそれぞれ異なるメッセージを表示し、一定の時間待機します。

メインスレッドは、すべてのスレッドが終了するのを待機します。

以下にサンプルコードを示します。

import java.util.ArrayList;
import java.util.List;
class MyRunnable implements Runnable {
    private String message; // 表示するメッセージ
    public MyRunnable(String message) {
        this.message = message; // メッセージを初期化
    }
    @Override
    public void run() {
        // スレッドの処理内容
        for (int i = 0; i < 5; i++) {
            System.out.println(Thread.currentThread().getName() + " - メッセージ: " + message + " カウント: " + i);
            try {
                Thread.sleep(200); // 200ミリ秒待機
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
public class App {
    public static void main(String[] args) throws InterruptedException {
        List<Thread> threads = new ArrayList<>(); // スレッドを格納するリスト
        
        // スレッドを作成
        for (int i = 0; i < 3; i++) {
            String message = "スレッド " + (i + 1); // 各スレッドに異なるメッセージを設定
            Thread thread = new Thread(new MyRunnable(message));
            threads.add(thread); // スレッドをリストに追加
            thread.start(); // スレッドを開始
        }
        
        // 各スレッドの終了を待機
        for (Thread thread : threads) {
            thread.join(); // スレッドの終了を待機
        }
        
        System.out.println("すべてのスレッドが終了しました。");
    }
}
  1. MyRunnableクラス: Runnableインターフェースを実装し、スレッドが実行する処理を定義します。

コンストラクタで受け取ったメッセージを表示します。

  1. メインメソッド: 3つのスレッドを作成し、それぞれに異なるメッセージを設定します。

スレッドを開始した後、join()メソッドを使用して、すべてのスレッドが終了するのを待機します。

このプログラムを実行すると、以下のような出力が得られます。

Thread-0 - メッセージ: スレッド 1 カウント: 0
Thread-1 - メッセージ: スレッド 2 カウント: 0
Thread-2 - メッセージ: スレッド 3 カウント: 0
Thread-0 - メッセージ: スレッド 1 カウント: 1
Thread-1 - メッセージ: スレッド 2 カウント: 1
Thread-2 - メッセージ: スレッド 3 カウント: 1
Thread-0 - メッセージ: スレッド 1 カウント: 2
Thread-1 - メッセージ: スレッド 2 カウント: 2
Thread-2 - メッセージ: スレッド 3 カウント: 2
Thread-0 - メッセージ: スレッド 1 カウント: 3
Thread-1 - メッセージ: スレッド 2 カウント: 3
Thread-2 - メッセージ: スレッド 3 カウント: 3
Thread-0 - メッセージ: スレッド 1 カウント: 4
Thread-1 - メッセージ: スレッド 2 カウント: 4
Thread-2 - メッセージ: スレッド 3 カウント: 4
すべてのスレッドが終了しました。

このように、Javaを使用して複数のスレッドを同時に実行し、終了を待機することができます。

この手法は、並行処理を行う際に非常に有用です。

スレッドの終了待機における注意点

スレッドの終了を待機する際には、いくつかの注意点があります。

これらの注意点を理解しておくことで、プログラムの安定性やパフォーマンスを向上させることができます。

以下に、主な注意点を挙げます。

デッドロックの回避

デッドロックは、複数のスレッドが互いにリソースを待ち続ける状態です。

これにより、スレッドが永久に終了しない可能性があります。

デッドロックを回避するためには、以下の方法が有効です。

  • リソースの取得順序を統一する。
  • タイムアウトを設定して、一定時間内にリソースを取得できない場合は処理を中断する。

InterruptedExceptionの処理

スレッドが待機中に割り込まれた場合、InterruptedExceptionがスローされます。

この例外を適切に処理しないと、スレッドが予期せず終了することがあります。

以下の点に注意してください。

  • join()メソッドやsleep()メソッドを使用する際は、InterruptedExceptionをキャッチして適切に処理する。
  • スレッドが割り込まれた場合の処理を明確に定義する。

スレッドプールの管理

ExecutorServiceを使用する場合、スレッドプールの管理が重要です。

以下の点に注意してください。

  • スレッドプールをシャットダウンする際は、shutdown()メソッドを使用し、すべてのタスクが完了するのを待つためにawaitTermination()を使用する。
  • スレッドプールを再利用する場合、適切に状態を管理し、リソースリークを防ぐ。

スレッドの優先度

スレッドの優先度を設定することで、スレッドの実行順序に影響を与えることができます。

ただし、優先度の設定は必ずしも保証されるわけではないため、過度に依存しないようにしましょう。

以下の点に注意してください。

  • スレッドの優先度を設定する際は、setPriority()メソッドを使用する。
  • 優先度の設定が実行結果に与える影響を理解し、必要に応じて調整する。

リソースの解放

スレッドが終了した後は、使用していたリソースを適切に解放することが重要です。

これにより、メモリリークやリソースの枯渇を防ぐことができます。

以下の点に注意してください。

  • スレッドが使用していたリソース(ファイル、ネットワーク接続など)を明示的に閉じる。
  • try-with-resources文を使用して、リソースの自動解放を行う。

これらの注意点を考慮することで、スレッドの終了待機に関する問題を未然に防ぎ、より安定したプログラムを作成することができます。

スレッドを適切に管理し、効率的にリソースを利用することが、Javaプログラミングにおける重要なスキルです。

まとめ

この記事では、Javaにおける複数のスレッドの同時実行とその終了待機の方法について詳しく解説しました。

スレッドの基本的な概念から、実際のコード例、注意点までを通じて、スレッドを効果的に管理するための知識を提供しました。

これを機に、実際のプロジェクトにおいてスレッドを活用し、パフォーマンスの向上を図ることを検討してみてください。

関連記事

Back to top button