1. スレッドへのパラメータの受け渡し
スレッドを起動するとき、しばしば何らかの「仕事」を渡したくなります。たとえばファイル名、数値の範囲、あいさつ文など。パラメータのないスレッドは、住所を知らない配達員のようなものです—街を走り回っても、どこにピザを届ければいいのかわかりません。
どうやって行うの?
Java ではスレッドは通常、次の2通りの方法で作成します:
- Thread クラスを継承する
- Runnable インターフェースを実装する(またはラムダ式を使う)
パラメータの受け渡しは、たいてい Runnable を実装するクラスのコンストラクタ経由で行います。シンプルで安全、しかも分かりやすい。フィールドを final にしておけば、他スレッドからパラメータを変更されるのを防げます。セッターや public フィールドの利用は、同期の問題を招きがちです。
例 1: コンストラクタ経由でパラメータを受け取るスレッド
学習用アプリを拡張してみましょう。ユーザーの注文処理を別スレッドで行います。
public class OrderProcessor implements Runnable {
private final String orderId;
public OrderProcessor(String orderId) {
this.orderId = orderId;
}
@Override
public void run() {
System.out.println("注文を処理中: " + orderId + " (スレッド " + Thread.currentThread().getName() + ")");
// 時間のかかる処理が入る...
}
}
スレッドを作成して起動する:
public class Main {
public static void main(String[] args) {
Thread thread1 = new Thread(new OrderProcessor("ORDER-001"));
Thread thread2 = new Thread(new OrderProcessor("ORDER-002"));
thread1.start();
thread2.start();
}
}
想定される出力:
注文を処理中: ORDER-001 (スレッド Thread-0)
注文を処理中: ORDER-002 (スレッド Thread-1)
例 2: ラムダ式(Java 8+)でパラメータを渡すスレッド
タスクが単純なら、別クラスを作らなくても済みます。
public class Main {
public static void main(String[] args) {
String user1 = "Vasya";
String user2 = "Katya";
Thread thread1 = new Thread(() -> {
System.out.println("こんにちは、" + user1 + "(" + Thread.currentThread().getName() + " より)");
});
Thread thread2 = new Thread(() -> {
System.out.println("こんにちは、" + user2 + "(" + Thread.currentThread().getName() + " より)");
});
thread1.start();
thread2.start();
}
}
なぜセッター/ゲッターでパラメータを渡すべきではないのか?
セッターや public フィールドでパラメータを渡すと、実行中に別スレッドが値を変更してしまうリスクがあります。これが原因のバグは見つけにくいものです。フィールドは final にし、コンストラクタで受け渡すのがベターです。
2. スレッドの優先度
Java の各スレッドには優先度があります。1 から 10 の整数で、スケジューラ(scheduler)に対して、そのスレッドが他よりどれくらい「重要」かを示します。デフォルトでは優先度は 5(Thread.NORM_PRIORITY)です。
重要: 優先度は OS への「ヒント」にすぎません。優先度が 10 のスレッドが、1 のスレッドより必ず速く動作するという保証ではありません。実際の挙動は OS、設定、現在の負荷などに左右されます。
優先度の設定方法は?
Thread クラスには次のメソッドがあります:
- setPriority(int newPriority)
- getPriority()
標準の3つの定数:
- Thread.MIN_PRIORITY(1)
- Thread.NORM_PRIORITY(5)
- Thread.MAX_PRIORITY(10)
例 3: スレッドに優先度を設定する
public class PriorityDemo {
public static void main(String[] args) {
Runnable task = () -> {
System.out.println("スレッド " + Thread.currentThread().getName() +
" の優先度 " + Thread.currentThread().getPriority());
};
Thread low = new Thread(task, "LowPriority");
Thread norm = new Thread(task, "NormalPriority");
Thread high = new Thread(task, "HighPriority");
low.setPriority(Thread.MIN_PRIORITY); // 1
norm.setPriority(Thread.NORM_PRIORITY); // 5
high.setPriority(Thread.MAX_PRIORITY); // 10
low.start();
norm.start();
high.start();
}
}
想定される出力(行順は保証されません):
スレッド LowPriority の優先度 1
スレッド HighPriority の優先度 10
スレッド NormalPriority の優先度 5
優先度は実行順序に影響するか?
多くの場合は—いいえ。優先度は CPU 時間の配分に影響することはあっても、開始や終了の順序を保証しません。プログラムのロジックを優先度に依存させないでください。例えばバックグラウンドのスレッドがメインを邪魔しないようにする、といった「やわらかな」ヒントとして使いましょう。
表: スレッド優先度の定数
| 定数 | 値 | 説明 |
|---|---|---|
|
1 | 最も低い優先度 |
|
5 | 標準の優先度 |
|
10 | 最も高い優先度 |
3. スレッドに名前を付ける
スレッド名は、マルチタスクの世界での「ニックネーム」です。多数のスレッドがあるとき、ログで "Thread-7" とあるより、"FileUploader-1" と見えたほうがデバッグしやすくなります。ログ解析や不具合調査で特に重要です。
スレッド名の設定方法
Thread のコンストラクタで直接指定するか、setName で設定します。現在の名前は getName で取得できます。
Thread t = new Thread(() -> {
System.out.println("動いています!");
}, "MyThreadName");
t.start();
例 4: 名前付きスレッド
public class NamedThreads {
public static void main(String[] args) {
Thread threadA = new Thread(() -> {
System.out.println("私はスレッド: " + Thread.currentThread().getName());
}, "Downloader");
Thread threadB = new Thread(() -> {
System.out.println("私はスレッド: " + Thread.currentThread().getName());
});
threadB.setName("Uploader");
threadA.start();
threadB.start();
}
}
想定される出力:
私はスレッド: Downloader
私はスレッド: Uploader
4. 実践: 異なるパラメータ・優先度・名前を持つ複数スレッド
すべてを1つの例にまとめます。各注文は独立したスレッドで、それぞれ固有の名前と優先度を持ちます。
public class OrderProcessor implements Runnable {
private final String orderId;
private final int processingTimeMs;
public OrderProcessor(String orderId, int processingTimeMs) {
this.orderId = orderId;
this.processingTimeMs = processingTimeMs;
}
@Override
public void run() {
System.out.println("[" + Thread.currentThread().getName() + "] 注文 " + orderId +
" の処理を開始(優先度 " + Thread.currentThread().getPriority() + ")");
try {
Thread.sleep(processingTimeMs); // 作業の模擬
} catch (InterruptedException e) {
System.out.println("[" + Thread.currentThread().getName() + "] 割り込まれました!");
}
System.out.println("[" + Thread.currentThread().getName() + "] 注文 " + orderId + " の処理を完了");
}
public static void main(String[] args) {
Thread fastOrder = new Thread(new OrderProcessor("FAST-ORDER", 500), "FastOrderThread");
Thread normalOrder = new Thread(new OrderProcessor("NORMAL-ORDER", 1000), "NormalOrderThread");
Thread slowOrder = new Thread(new OrderProcessor("SLOW-ORDER", 2000), "SlowOrderThread");
fastOrder.setPriority(Thread.MAX_PRIORITY);
normalOrder.setPriority(Thread.NORM_PRIORITY);
slowOrder.setPriority(Thread.MIN_PRIORITY);
fastOrder.start();
normalOrder.start();
slowOrder.start();
}
}
観察できること:
- 各スレッドが、どの注文を処理しているか、自分の名前と優先度を出力します。
- 処理時間は Thread.sleep で擬似化しています。
- スレッドの終了順序は、優先度と一致しない場合があります。
5. 重要な注意点と特性
パラメータの受け渡し: コンストラクタ経由だけ!
このほうが安全です。パラメータを final に宣言すれば、オブジェクト生成後に変更できず、他スレッドが勝手に値を差し替える心配もありません。データ競合を防ぐうえで重要です。
優先度: それにロジックを依存させない
優先度はウェイターへのお願い「コーヒーを早めにお願いします」のようなもの。効くときもあれば、そうでないときもあります。OS への推奨であって、実行順序の規則ではありません。
スレッド名: デバッグに活用する
ログ解析の時間を大幅に節約できます。意味のある名前を最初から付ける癖をつけましょう。
6. パラメータと優先度に関する典型的なミス
エラー №1: public フィールドでパラメータを渡す。 パラメータを通常のフィールドとして宣言し、スレッド開始後に変更すると、結果が予測不能になります。final フィールドとコンストラクタを使いましょう。
エラー №2: 優先度が順序を保証すると期待する。 Thread.MAX_PRIORITY を設定したからといって、常にそのスレッドが先頭になるわけではありません。同期やロジック制御に優先度を使わないでください。
エラー №3: ログに名前のないスレッド。 ログが「Thread-3」「Thread-7」だけでは、原因の特定が難しくなります。コンストラクタや setName で意味のある名前を付けましょう。
エラー №4: 同じ Runnable オブジェクトを複数スレッドで使う。 Runnable オブジェクトが状態を持ち、複数スレッドに渡していると、パラメータを「共有」してしまい、データ競合への近道です。スレッドごとに個別の Runnable を生成しましょう。
エラー №5: set メソッドでパラメータを渡す。 生成/開始後に set* メソッドで値を設定すると、実行中に値が「すり替わる」可能性があります—典型的な race condition です。
GO TO FULL VERSION