1. 実行スレッド (thread) とは
スレッドは独立した作業の流れ
Java(そして一般のプログラミング)において、実行スレッドとは同一プログラム内で他のスレッドと並行して進む独立した命令列です。工場を想像してください。各縫い手には自分の作業台と担当作業があり、独立して働きますが、全体としては一つの成果にまとまります。
通常、Java プログラムは 1 本のスレッド――メソッド main を起動するスレッド――から始まります。しかし、プログラムの異なる部分を同時に実行させるために、追加のスレッドを作成できます。
プロセスとスレッド:何が違う?
- プロセス は「重量級」の実行単位です。各プロセスは独自のメモリ空間、変数、リソースを持ちます。プロセス同士は完全に隔離されているため、1 つが故障しても他は影響を受けません。
- スレッド (thread) はプロセス内の「軽量」な実行単位です。同一プロセスのスレッドはメモリとリソースを共有します。つまりデータのやり取りは容易ですが、残念ながら互いに干渉もしやすくなります。
たとえ:
プロセスは別のマンションの一室のようなもの。壁も住人もそれぞれ別々。
スレッドは同じ部屋の住人たち。各自の用事はあるが、キッチンやバスルームは共有。
Java ではどう見える?
プログラムを起動すると、JVM は少なくとも 1 本、すなわちメイン(main)のスレッドを作成します。さらに、タスクを並行して実行するために新しいスレッドを作成できます。
2. なぜマルチスレッドが必要か
応答性:UI を「フリーズ」させない
たとえば、あなたがグラフィカルなアプリ――たとえばテキストエディタ――を書いているとします。ユーザーが「保存」ボタンを押し、あなたは長時間ファイルを書き込みます。これをメインスレッドで行うと、ウィンドウは「フリーズ」して、ユーザーは何も操作できず、カーソルも動かず、インターフェースが反応しません。保存を別スレッドで行えば、UI は応答性を保ち、ユーザーは気が変わってアプリを閉じることさえできます。
身近な例:
ブラウザで大きなファイルをダウンロードするとします。もしブラウザがスレッドを使わなかったら、ダウンロードが終わるまで新しいタブを開くことも、ページをスクロールすることもできません!
データの並列処理
たとえば、処理すべき 1000 個のファイルのリストがあるとします(ハッシュの再計算やテキスト置換など)。これらを並列に処理してはどうでしょうか。各スレッドが自分のファイルを受け持って独立に処理すれば、全体の作業は数倍速く終わります。
例:
サーバは数百のクライアントからのリクエストを処理します。もし 1 本のスレッドだけで処理したら、他のクライアントは永遠に順番待ちです。スレッドを使えば各リクエストを独立に処理できます!
マルチコアプロセッサの活用
現代のプロセッサは 1 個の「脳」ではなく、並行して働ける複数のコアのチームです。あなたのプログラムが 1 本のスレッドしか使わないなら、他のコアは手持ち無沙汰。複数スレッドを走らせれば、すべてのコアが仕事をし、プログラムはより速く実行されます。
豆知識:
スマートフォンでさえ複数コア、ノート PC やサーバは何十コアもあります!それらを使わないのは、大きなバスを買って一人で乗るようなものです。
3. 実社会の例
| 分野 | マルチスレッドの例 |
|---|---|
| ファイルのダウンロード | 複数ファイルの同時ダウンロード |
| ユーザー UI | データの読み込み/保存中でもアプリが「フリーズ」しない |
| サーバ | 多数のネットワークリクエストを並行処理 |
| ゲーム | 物理、グラフィックス、音楽、AI を別スレッドで |
| メッセージング | メッセージ受信、ファイル送信、UI 更新 |
| 動画処理 | フレームを並列処理 |
ミニたとえ:
シェフがスープを作り、同時にオーブンはケーキを焼き、ロボット掃除機が床を掃除――すべてが同時に進み、夕食の準備が早くなる!
4. マルチスレッドの潜在的な難しさ
競合状態(race condition)
複数のスレッドが同じ変数を同時に更新すると、結果は予測不能になります。たとえば 2 本のスレッドが同時に共有カウンタをインクリメントすると、最終値が正しくないことがあります。これについては後続の講義で詳しく扱います。
同期
スレッド同士が邪魔し合わないよう、「誰がいつデータを変えてよいか」を取り決める必要があります。これが同期です。これには synchronized やロックなどの構文・仕組みがあります。詳細は後ほど説明します。
Deadlock(相互待ち)
スレッドが互いを永遠に待ち続け、プログラムが停止することがあります。これがデッドロックで、マルチスレッドプログラミングで最も厄介なバグの一つです。
デバッグとテスト
マルチスレッドのバグは捕まえるのが非常に難しいものです。あるときは動き、あるときは動かない。サーバやユーザー環境でだけ再現し、自分の PC では再現しないことも。これがマルチスレッドコードのテストとデバッグを開発者にとって本当のクエストにします。
5. ざっくり俯瞰:マルチスレッドのプログラムはこう見える
スレッドなしの例:
public class Main {
public static void main(String[] args) {
// 5 まで数える
for (int i = 1; i <= 5; i++) {
System.out.println(i);
}
// 文字を出力する
for (char c = 'A'; c <= 'E'; c++) {
System.out.println(c);
}
}
}
出力は常に同じ:
1
2
3
4
5
A
B
C
D
E
スレッドありの例:
public class Main {
public static void main(String[] args) {
Thread numbers = new Thread(() -> {
for (int i = 1; i <= 5; i++) {
System.out.println(i);
try {
Thread.sleep(100); // 少し待つ
} catch (InterruptedException e) {
// 無視する
}
}
});
Thread letters = new Thread(() -> {
for (char c = 'A'; c <= 'E'; c++) {
System.out.println(c);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// 無視する
}
}
});
numbers.start();
letters.start();
}
}
出力は混ざる:
1
A
2
B
3
C
4
D
5
E
あるいは、スレッドが「競い合う」と順序が別になることもあります。重要なのは――両方のループが並行して進むことです!
6. 役立つポイント
ビジュアル図解:スレッドはこうやって一緒に動く
+-------------------+ +-------------------+
| Main thread | | Second thread |
+-------------------+ +-------------------+
| 1 | 2 | 3 | 4 | 5 | | A | B | C | D | E |
+-------------------+ +-------------------+
| |
| Both run |
| at the same time |
+-------------------------+
Java が「内部で」スレッドを使っている場所
- ガーベジコレクタ(Garbage Collector) ― 未使用オブジェクトを掃除する専用スレッド。
- 入出力(I/O) ― ファイルの読み書き、ネットワーク接続。
- サーバと Web アプリ ― 各クライアントリクエストを別スレッドで処理。
- タイマー、タスクスケジューラ ― スケジュールされた処理の実行。
7. 初心者がよく犯すミス
ミス No.1:スレッドは常にプログラムを高速化すると期待する。
実際には、シングルプロセッサ環境だったり、設計がまずいと、スレッド切り替えのオーバーヘッドや「混乱」により、かえって遅くなることがあります。
ミス No.2:同期の問題を無視する。
「ただ 2 本のスレッドを走らせるだけ、問題になるはずがない」と考えがちですが、両方が同じ変数を変更すると、結果はまったく予想外になり得ます。
ミス No.3:なんでもかんでもスレッドで行う。
くしゃみのたびにスレッドを立ち上げるようなことはやめましょう。スレッドはリソースであり、多すぎると遅延やクラッシュの原因になります。
ミス No.4:エラー処理をしない。
スレッドは例外を投げることがあります(ファイルやネットワーク操作など)。これらを処理しないと、プログラムが異常終了したり「フリーズ」したりします。
GO TO FULL VERSION