Javaマルチスレッド完全ガイド:効率的な並行プログラミング
マルチスレッドとは
Javaはマルチスレッドプログラミング言語であり、複数のスレッドを持つプログラムを開発できます。マルチスレッドプログラムは、同時に実行可能な2つ以上の部分を含み、それぞれが異なるタスクを同時に処理するため、特にマルチCPU環境では利用可能なリソースを最適に活用できます。
定義上、マルチタスクとは複数のプロセスがCPUなどの共通処理リソースを共有することです。マルチスレッドはこの概念をアプリケーションレベルに拡張し、単一アプリケーション内の特定操作を個別のスレッドに細分化します。各スレッドは並列に実行可能です。OSは異なるアプリケーション間だけでなく、アプリケーション内の各スレッド間でも処理時間を分配します。
マルチスレッドにより、同一プログラム内で複数のアクティビティを同時並行的に進行させる記述が可能になります。
スレッドのライフサイクル
スレッドはその生涯で様々な段階を経ます。例えば、スレッドは生成され、開始され、実行され、そして終了します。以下の図はスレッドの完全なライフサイクルを示しています。
ライフサイクルの段階は以下の通りです:
- 新規 - 新しいスレッドは新規状態でライフサイクルを開始します。プログラムがスレッドを開始するまでこの状態に留まります。生成されたスレッドとも呼ばれます。
- 実行可能 - 新しく生成されたスレッドが開始されると、実行可能状態になります。この状態のスレッドはタスクを実行中と見なされます。
- 待機 - スレッドが別のスレッドのタスク完了を待っている間、待機状態に移行することがあります。別のスレッドが待機スレッドに継続を通知すると、実行可能状態に戻ります。
- タイムアウト付き待機 - 実行可能なスレッドは、指定された時間間隔でタイムアウト付き待機状態に入ることができます。時間が経過するか、待機していたイベントが発生すると、実行可能状態に戻ります。
- 終了 - 実行可能なスレッドがタスクを完了するか、何らかの理由で終了すると、終了状態に入ります。
スレッドの優先度
すべてのJavaスレッドには優先度があり、OSがスレッドのスケジューリング順序を決定する際の指針となります。
Javaスレッドの優先度は、MIN_PRIORITY(定数値1)からMAX_PRIORITY(定数値10)の範囲です。デフォルトでは、すべてのスレッドにNORM_PRIORITY(定数値5)が割り当てられます。
優先度の高いスレッドはプログラムにとってより重要であり、低優先度スレッドより先にプロセッサ時間が割り当てられるべきです。ただし、スレッド優先度は実行順序を保証するものではなく、プラットフォームに大きく依存します。
Runnableインターフェースによるスレッド作成
クラスをスレッドとして実行する意図がある場合、Runnableインターフェースを実装することで実現できます。以下の3つの基本ステップに従ってください。
ステップ1
最初のステップとして、Runnableインターフェースが提供するrun()メソッドを実装します。このメソッドはスレッドのエントリーポイントとなり、完全なビジネスロジックをこのメソッド内に記述します。run()メソッドのシンプルな構文は以下の通りです。
public void run()
ステップ2
2番目のステップとして、以下のコンストラクタを使用してThreadオブジェクトをインスタンス化します。
Thread(Runnable threadObj, String threadName);
ここで、threadObjはRunnableインターフェースを実装したクラスのインスタンス、threadNameは新しいスレッドに与える名前です。
ステップ3
Threadオブジェクトが作成されたら、start()メソッドを呼び出して開始します。これによりrun()メソッドの呼び出しが実行されます。start()メソッドのシンプルな構文は以下の通りです。
void start();
サンプル
新しいスレッドを作成して実行する例です。
Live Democlass RunnableDemo implements Runnable {
private Thread t;
private String threadName;
RunnableDemo(String name) {
threadName = name;
System.out.println("Creating " + threadName);
}
public void run() {
System.out.println("Running " + threadName);
try {
for(int i = 4; i > 0; i--) {
System.out.println("Thread: " + threadName + ", " + i);
// スレッドを少しスリープ
Thread.sleep(50);
}
} catch (InterruptedException e) {
System.out.println("Thread " + threadName + " interrupted.");
}
System.out.println("Thread " + threadName + " exiting.");
}
public void start() {
System.out.println("Starting " + threadName);
if (t == null) {
t = new Thread(this, threadName);
t.start();
}
}
}
public class TestThread {
public static void main(String args[]) {
RunnableDemo R1 = new RunnableDemo("Thread-1");
R1.start();
RunnableDemo R2 = new RunnableDemo("Thread-2");
R2.start();
}
}
実行結果
Creating Thread-1 Starting Thread-1 Creating Thread-2 Starting Thread-2 Running Thread-1 Thread: Thread-1, 4 Running Thread-2 Thread: Thread-2, 4 Thread: Thread-1, 3 Thread: Thread-2, 3 Thread: Thread-1, 2 Thread: Thread-2, 2 Thread: Thread-1, 1 Thread: Thread-2, 1 Thread Thread-1 exiting. Thread Thread-2 exiting.
Threadクラスを継承したスレッド作成
スレッドを作成する2番目の方法は、Threadクラスを継承した新しいクラスを作成することです。このアプローチは、Threadクラスで利用可能なメソッドを使用して複数のスレッドを処理する際により柔軟性があります。
ステップ1
Threadクラスで利用可能なrun()メソッドをオーバーライドする必要があります。このメソッドはスレッドのエントリーポイントとなり、完全なビジネスロジックをこのメソッド内に記述します。
public void run()
ステップ2
Threadオブジェクトが作成されたら、start()メソッドを呼び出して開始します。
void start();
サンプル
前のプログラムをThreadクラス継承で書き直した例です。
Live Democlass ThreadDemo extends Thread {
private Thread t;
private String threadName;
ThreadDemo(String name) {
threadName = name;
System.out.println("Creating " + threadName);
}
public void run() {
System.out.println("Running " + threadName);
try {
for(int i = 4; i > 0; i--) {
System.out.println("Thread: " + threadName + ", " + i);
Thread.sleep(50);
}
} catch (InterruptedException e) {
System.out.println("Thread " + threadName + " interrupted.");
}
System.out.println("Thread " + threadName + " exiting.");
}
public void start() {
System.out.println("Starting " + threadName);
if (t == null) {
t = new Thread(this, threadName);
t.start();
}
}
}
public class TestThread {
public static void main(String args[]) {
ThreadDemo T1 = new ThreadDemo("Thread-1");
T1.start();
ThreadDemo T2 = new ThreadDemo("Thread-2");
T2.start();
}
}
実行結果
Creating Thread-1 Starting Thread-1 Creating Thread-2 Starting Thread-2 Running Thread-1 Thread: Thread-1, 4 Running Thread-2 Thread: Thread-2, 4 Thread: Thread-1, 3 Thread: Thread-2, 3 Thread: Thread-1, 2 Thread: Thread-2, 2 Thread: Thread-1, 1 Thread: Thread-2, 1 Thread Thread-1 exiting. Thread Thread-2 exiting.
Threadクラスの主要メソッド
Threadクラスで利用可能な重要なメソッドの一覧です。
| 番号 | メソッドと説明 |
|---|---|
| 1 | public void start() スレッドを別の実行パスで開始し、このThreadオブジェクトでrun()メソッドを呼び出します。 |
| 2 | public void run() このThreadオブジェクトが別のRunnableターゲットでインスタンス化された場合、そのRunnableオブジェクトでrun()メソッドが呼び出されます。 |
| 3 | public final void setName(String name) Threadオブジェクトの名前を変更します。名前取得用のgetName()メソッドもあります。 |
| 4 | public final void setPriority(int priority) このThreadオブジェクトの優先度を設定します。取りうる値は1〜10です。 |
| 5 | public final void setDaemon(boolean on) trueを指定すると、このスレッドをデーモンスレッドとして指定します。 |
| 6 | public final void join(long millisec) 現在のスレッドがこのメソッドを別のスレッドで呼び出し、そのスレッドが終了するか、指定ミリ秒が経過するまでブロックします。 |
| 7 | public void interrupt() このスレッドを割り込み、何らかの理由でブロックされていた場合に実行を継続させます。 |
| 8 | public final boolean isAlive() スレッドが開始済みだがまだ完了していない場合(生存中)にtrueを返します。 |
上記メソッドは特定のThreadオブジェクトで呼び出されます。以下のThreadクラスのメソッドは静的メソッドであり、現在実行中のスレッドに対して操作を行います。
| 番号 | メソッドと説明 |
|---|---|
| 1 | public static void yield() 現在実行中のスレッドを、スケジューリングを待っている同じ優先度の他のスレッドに譲ります。 |
| 2 | public static void sleep(long millisec) 現在実行中のスレッドを、指定ミリ秒以上ブロックします。 |
| 3 | public static boolean holdsLock(Object x) 現在のスレッドが指定オブジェクトのロックを保持している場合にtrueを返します。 |
| 4 | public static Thread currentThread() 現在実行中のスレッド(このメソッドを呼び出したスレッド)への参照を返します。 |
| 5 | public static void dumpStack() 現在実行中のスレッドのスタックトレースを出力します。マルチスレッドアプリケーションのデバッグに有用です。 |
スレッドメソッドのデモ
以下のThreadClassDemoプログラムは、Threadクラスのいくつかのメソッドを示しています。DisplayMessageクラスはRunnableを実装します。
// ファイル名: DisplayMessage.java
public class DisplayMessage implements Runnable {
private String message;
public DisplayMessage(String message) { this.message = message; }
public void run() {
while(true) { System.out.println(message); }
}
}
Threadクラスを継承した別のクラス:
// ファイル名: GuessANumber.java
public class GuessANumber extends Thread {
private int number;
public GuessANumber(int number) { this.number = number; }
public void run() {
int counter = 0;
int guess = 0;
do {
guess = (int) (Math.random() * 100 + 1);
System.out.println(this.getName() + " guesses " + guess);
counter++;
} while(guess != number);
System.out.println("** Correct!" + this.getName() + " in " + counter + " guesses.**");
}
}
メインプログラム:
// ファイル名: ThreadClassDemo.java
public class ThreadClassDemo {
public static void main(String[] args) {
Runnable hello = new DisplayMessage("Hello");
Thread thread1 = new Thread(hello);
thread1.setDaemon(true);
thread1.setName("hello");
System.out.println("Starting hello thread...");
thread1.start();
Runnable bye = new DisplayMessage("Goodbye");
Thread thread2 = new Thread(bye);
thread2.setPriority(Thread.MIN_PRIORITY);
thread2.setDaemon(true);
System.out.println("Starting goodbye thread...");
thread2.start();
System.out.println("Starting thread3...");
Thread thread3 = new GuessANumber(27);
thread3.start();
try { thread3.join(); } catch (InterruptedException e) { System.out.println("Thread interrupted."); }
System.out.println("Starting thread4...");
Thread thread4 = new GuessANumber(75);
thread4.start();
System.out.println("main() is ending...");
}
}
実行結果
Starting hello thread... Starting goodbye thread... Hello Hello Hello Hello Hello Hello Goodbye Goodbye Goodbye Goodbye Goodbye .......
Javaマルチスレッドの主要概念
Javaでマルチスレッドプログラミングを行う際、以下の概念をしっかり理解しておく必要があります:
- スレッド同期とは何か?
- スレッド間通信の処理
- スレッドデッドロックの処理
- 主要なスレッド操作
Java
- C++マルチスレッド完全ガイド:効率的な並行プログラミング
- Java演算子をマスター:種類、構文、実践例
- Javaコメント:種類、使い方、ベストプラクティス
- Java拡張forループをマスター:構文、例、ベストプラクティス
- Java文字列をマスター:生成、メソッド、ベストプラクティス
- Javaインターフェース完全ガイド:概念、実装、ベストプラクティス
- Java無名インナークラス完全ガイド:定義、構文、実践例
- Javaのtry-with-resourcesをマスターする:自動リソース管理の仕組みと活用法
- Javaアノテーション完全ガイド:種類、配置場所、実践例
- Javaアサーション完全ガイド:バグ早期発見とコード信頼性の向上
- Java Vectorの理解:使い方、特徴、ArrayListとの比較