
Java超|スレッドの流れを制御する
止める、待つ、タイミングをそろえる。スレッドの流れを制御できると、複数の処理がただ動くだけでなく、意図した順番や間合いで進められるようになります。
スレッドの基本では、main メソッドとは別に、新しい処理の流れを作れることを学びました。
Thread クラスを継承したクラスを作り、run メソッドに別スレッドで行いたい処理を書く。
そして start メソッドを呼び出すことで、main の流れとは別に、もう1本の処理の流れを動かせました。
ただし、実際のプログラムでは、スレッドを起動するだけでは足りない場面があります。
複数のスレッドが自由に動けるようになると、次に大切になるのは 流れをどう整えるか です。
たとえば、次のような場面を考えてみます。
| やりたいこと | 必要になる制御 |
|---|---|
| 1秒ごとにメッセージを表示したい | スレッドを一定時間だけ止める |
| 片方の処理をゆっくり進めたい | sleep で間を作る |
| 別スレッドの処理が終わるまで待ちたい | join で終了を待つ |
| 最後の確認処理を必ずあとに実行したい | join で順番をそろえる |
ドラゴンボール風にたとえると、戦士本部の任務では、戦士たちが勝手に動くだけでは作戦になりません。
GokuWarrior が重力修行を進めるとき、あえて一呼吸置いてから次の動きへ移ることがあります。
VegetaWarrior が戦闘記録をまとめ終えるまで、BulmaSupport が最終確認を待つこともあります。
つまり、複数の戦士が動く任務では、次のような考え方が必要になります。
| 制御 | ドラゴンボール風のイメージ |
|---|---|
| sleep | 少し気を整えてから次の動きへ進む |
| join | 仲間の任務完了を待ってから次へ進む |
Javaのスレッドでも同じです。
スレッドは並行して動けます。
しかし、ただ自由に動かすだけでは、出力順や処理のタイミングが分かりにくくなることがあります。
そこで、sleep メソッドや join メソッドを使って、スレッドの流れを調整します。
この記事では、スレッドを一時停止する sleep メソッドと、別スレッドの終了を待つ join メソッドを中心に、スレッドの流れを制御する考え方を整理します。
具体的な例示プログラムは Sample3.java、Sample4.java、Sample5.java を使います。
スレッドは起動したあとも操作できる
スレッドは start で起動したら終わり、というものではありません。
起動したあとのスレッドに対して、次のような制御ができます。
| 操作 | 役割 |
|---|---|
| sleep | 現在動いているスレッドを一定時間だけ一時停止する |
| join | 指定したスレッドが終わるまで、呼び出した側のスレッドを待たせる |
ここで大切なのは、どちらも「流れを整えるための道具」だということです。
スレッドを複数使うと、main の流れと別スレッドの流れがそれぞれ進みます。
そのため、次のような視点が必要になります。
| 状況 | 必要な考え方 |
|---|---|
| 出力が速すぎる | sleep で少し間を空ける |
| 先に終わってほしい処理がある | join で終了を待つ |
| 順番をそろえたい | どのスレッドを待つのかを決める |
| 一部だけ止めたい | sleep を書く場所に注意する |
ドラゴンボール風に言えば、任務中に全員が同時に飛び出すだけでは、連携が乱れることがあります。
GokuWarrior が気を整えるために一瞬止まる。
VegetaWarrior の任務完了を待ってから、BulmaSupport が最終確認を出す。
このように、流れを増やすだけでなく、流れを整えることが大切です。
sleep メソッドとは何か
sleep メソッドは、現在動いているスレッドを一定時間だけ一時停止するためのメソッドです。
たとえば、次のように書きます。
Thread.sleep(1000);1000 はミリ秒を表します。
つまり、Thread.sleep(1000) は、現在のスレッドを1秒間止めるという意味です。
| 書き方 | 意味 |
|---|---|
| Thread.sleep(1000); | 現在のスレッドを1秒止める |
| Thread.sleep(500); | 現在のスレッドを0.5秒止める |
| Thread.sleep(2000); | 現在のスレッドを2秒止める |
ここで特に大切なのは、止まるのは sleep を実行したスレッドだけ という点です。
sleep を書いたからといって、プログラム全体が止まるわけではありません。
たとえば、GokuWarrior スレッドの run の中で sleep を実行した場合、止まるのは GokuWarrior スレッドです。
main スレッドは、その間も動けます。
ドラゴンボール風に言えば、GokuWarrior が一呼吸置いて気を整えていても、BulmaSupport まで同時に止まるわけではありません。
止まるのは、その場で気を整えている GokuWarrior だけです。
sleep で止まるのは現在のスレッド
sleep を理解するときは、次の表が大切です。
| sleep を書く場所 | 一時停止するスレッド |
|---|---|
| run の中 | その run を実行している別スレッド |
| main の中 | main スレッド |
| warrior1 の run の中 | warrior1 のスレッド |
| warrior2 の run の中 | warrior2 のスレッド |
sleep は、指定した相手を外から止める命令ではありません。
今その命令を実行しているスレッド自身が止まる と考えると分かりやすいです。
この性質を押さえると、Sample3.java と Sample4.java の違いが見えやすくなります。
図:sleep は現在のスレッドだけを一時停止する

この図が示していること
この図は、sleep がプログラム全体ではなく、sleep を実行したスレッドだけを一時停止することを表しています。
main スレッドは、BulmaSupport の見回りをそのまま進めます。
一方、GokuWarrior スレッドは、Thread.sleep(1000) の場所で1秒ずつ止まりながら進みます。
| 図の要素 | 意味 |
|---|---|
| mainスレッド | 止まらずに進む処理の流れ |
| GokuWarriorスレッド | sleep によって一時停止する処理の流れ |
| 砂時計アイコン | 一定時間だけ待つこと |
| Thread.sleep(1000) | 現在のスレッドを1秒止める処理 |
この図から分かることは、sleep は「すべての処理を止める命令」ではなく、「今動いているそのスレッドを休ませる命令」だということです。
sleep で今動いているスレッドの処理を止める
ここでは、別スレッド側の run メソッドの中で sleep を使う例を見ていきます。
GokuWarrior が1秒ごとに気を整えながら修行を進める一方で、main 側では BulmaSupport が修行場を見回すイメージです。
ファイル名:Sample3.java
class WarriorTask extends Thread
{
private String name;
public WarriorTask(String nm)
{
name = nm;
}
public void run()
{
for(int i = 0; i < 5; i++) {
try {
// このスレッドを1秒だけ休ませる
sleep(1000);
System.out.println(name + "が気を整えています。");
}
catch(InterruptedException e) {
}
}
}
}
class Sample3
{
public static void main(String[] args)
{
WarriorTask warrior1 = new WarriorTask("GokuWarrior");
warrior1.start();
for(int i = 0; i < 5; i++) {
System.out.println("BulmaSupportが修行場を見回しています。");
}
}
}Sample3.java の全体像
このプログラムでは、WarriorTask クラスが Thread クラスを継承しています。
class WarriorTask extends Threadそのため、WarriorTask のオブジェクトは start で別スレッドとして起動できます。
main メソッドでは、GokuWarrior という名前の WarriorTask オブジェクトを作っています。
WarriorTask warrior1 = new WarriorTask("GokuWarrior");
warrior1.start();この start によって、GokuWarrior スレッドが起動します。
一方、main 側では、BulmaSupport が修行場を見回しているメッセージを5回表示します。
for(int i = 0; i < 5; i++) {
System.out.println("BulmaSupportが修行場を見回しています。");
}つまり、このプログラムには2つの流れがあります。
| 流れ | 内容 |
|---|---|
| main スレッド | BulmaSupport が修行場を見回す |
| GokuWarrior スレッド | 1秒ずつ待ちながら気を整える |
run の中で sleep を使っている
GokuWarrior スレッド側の run メソッドを見てみます。
public void run()
{
for(int i = 0; i < 5; i++) {
try {
sleep(1000);
System.out.println(name + "が気を整えています。");
}
catch(InterruptedException e) {
}
}
}ここでは、for 文の中で毎回 sleep(1000) を実行しています。
そのため、GokuWarrior スレッドは1回メッセージを表示する前に、1秒ずつ一時停止します。
流れは次のようになります。
| 順番 | GokuWarrior スレッドの動き |
|---|---|
| 1 | sleep(1000) で1秒待つ |
| 2 | GokuWarriorが気を整えています。と表示 |
| 3 | もう一度 sleep(1000) で1秒待つ |
| 4 | また表示 |
| 5 | これを5回繰り返す |
一方、main 側には sleep がありません。
そのため、BulmaSupport のメッセージは比較的すばやく表示されやすくなります。
| 流れ | 動き方 |
|---|---|
| main スレッド | ほぼ連続して表示する |
| GokuWarrior スレッド | 1秒ごとにゆっくり表示する |
ドラゴンボール風に言えば、BulmaSupport は修行場を連続して見回っています。
その一方で、GokuWarrior は次の動きに入る前に一呼吸ずつ気を整えながら、ゆっくり修行を進めています。
sleep の時間指定
sleep の引数はミリ秒です。
そのため、秒に直すと次のようになります。
| 指定 | 時間 |
|---|---|
| 1000 | 1秒 |
| 500 | 0.5秒 |
| 2000 | 2秒 |
| 3000 | 3秒 |
Sample3.java では、sleep(1000) と書いています。
sleep(1000);これは、GokuWarrior スレッドを1秒間一時停止するという意味です。
ここで main スレッドまで止まるわけではありません。
止まるのは、sleep を実行している GokuWarrior スレッドです。
InterruptedException に備える理由
sleep を使うときには、try-catch が必要になります。
try {
sleep(1000);
System.out.println(name + "が気を整えています。");
}
catch(InterruptedException e) {
}これは、sleep が InterruptedException を送出する可能性があるためです。
InterruptedException は、スレッドが待機中に割り込みを受けた場合などに関係する例外です。
学習段階では、まず次のように押さえると分かりやすいです。
| ポイント | 内容 |
|---|---|
| sleep を使う | InterruptedException に備える必要がある |
| try に書く | sleep を含む処理を書く |
| catch に書く | 割り込みが起きた場合の対応を書く |
ドラゴンボール風に言えば、GokuWarrior が気を整えている最中に、急な通信が届く可能性があるようなものです。
その可能性に備えるため、try-catch の形で囲んでいます。
main 側を一時停止する場合
次は、main スレッド側を一時停止する例を見ます。
Sample3.java では、run の中に sleep がありました。
そのため、GokuWarrior スレッドがゆっくり進みました。
一方、Sample4.java では、main の中に Thread.sleep(1000) を書きます。
これにより、一時停止するのは main スレッドになります。
ファイル名:Sample4.java
class WarriorTask extends Thread
{
private String name;
public WarriorTask(String nm)
{
name = nm;
}
public void run()
{
for(int i = 0; i < 5; i++) {
System.out.println(name + "が連続して戦闘フォームを確認しています。");
}
}
}
class Sample4
{
public static void main(String[] args)
{
WarriorTask warrior1 = new WarriorTask("VegetaWarrior");
warrior1.start();
for(int i = 0; i < 5; i++) {
try {
Thread.sleep(1000);
System.out.println("BulmaSupportが作戦メモを読み返しています。");
}
catch(InterruptedException e) {
}
}
}
}Sample4.java の流れ
このプログラムでは、VegetaWarrior スレッドの run の中に sleep はありません。
public void run()
{
for(int i = 0; i < 5; i++) {
System.out.println(name + "が連続して戦闘フォームを確認しています。");
}
}そのため、VegetaWarrior スレッドはメッセージを比較的すばやく表示しやすくなります。
一方、main 側では、for 文の中で Thread.sleep(1000) を実行しています。
for(int i = 0; i < 5; i++) {
try {
Thread.sleep(1000);
System.out.println("BulmaSupportが作戦メモを読み返しています。");
}
catch(InterruptedException e) {
}
}そのため、main スレッドは1秒ずつ待ちながら進みます。
| スレッド | sleep の有無 | 動き方 |
|---|---|---|
| VegetaWarrior スレッド | なし | 連続して進みやすい |
| main スレッド | あり | 1秒ごとに進む |
ここで大切なのは、sleep をどこに書いたかで止まる流れが変わることです。
| sleep を書く場所 | 止まるスレッド |
|---|---|
| run の中 | その run を動かしている別スレッド |
| main の中 | main スレッド |
ドラゴンボール風に言えば、Sample4.java では VegetaWarrior が戦闘フォーム確認を連続して進めます。
一方、BulmaSupport は作戦メモを1秒ごとに読み返して、ゆっくり確認しています。
Thread.sleep と書く理由
Sample4.java では、次のように書いています。
Thread.sleep(1000);これは、sleep が Thread クラスのクラスメソッドとして用意されているためです。
main メソッドは WarriorTask クラスの中ではなく、Sample4 クラスの中にあります。
そのため、Thread.sleep(1000) のように、Thread クラス名を付けて呼び出しています。
| 書き方 | 使われやすい場所 |
|---|---|
| sleep(1000); | Thread を継承したクラスの run の中 |
| Thread.sleep(1000); | main など、Thread クラス名を明示したい場所 |
どちらの場合でも、意味は「現在のスレッドを一時停止する」です。
Thread.sleep(1000) と書いたからといって、すべてのスレッドを止めるわけではありません。
止まるのは、今 Thread.sleep(1000) を実行しているスレッドです。
図:sleep を書く場所で止まる流れが変わる

この図が示していること
この図は、sleep をどこに書くかによって、一時停止するスレッドが変わることを表しています。
Sample3.java では、run の中に sleep があります。
そのため、GokuWarrior スレッドが止まります。
Sample4.java では、main の中に Thread.sleep があります。
そのため、main スレッドが止まります。
| 比較 | sleep の場所 | 一時停止するスレッド |
|---|---|---|
| Sample3.java | run の中 | GokuWarrior スレッド |
| Sample4.java | main の中 | main スレッド |
この図から分かることは、sleep は「指定した相手を止める命令」ではなく、「その命令を実行している現在のスレッドを止める命令」だということです。
sleep の使いどころ
sleep は、スレッドの流れに少し間を入れたいときに使います。
たとえば、次のような場面です。
| 使いどころ | 例 |
|---|---|
| 一定間隔で表示したい | 1秒ごとにメッセージを出す |
| 動きを見やすくしたい | 出力が速すぎないようにする |
| 少し待ってから次へ進みたい | 処理のタイミングを調整する |
| デモ用の動きを作りたい | 画面に変化を見せながら進める |
ドラゴンボール風に言えば、戦士が技と技の間に気を整えるようなものです。
速く動くだけではなく、一定の間合いを作ることで、流れが見えやすくなります。
ただし、sleep はあくまで時間で待つ方法です。
「相手の処理が終わるまで待つ」ためには、別のメソッドを使います。
それが join です。
join メソッドとは何か
join メソッドは、指定したスレッドが終わるまで、自分の処理を待機させるためのメソッドです。
たとえば、次のように書きます。
warrior1.join();これは、warrior1 のスレッドが終わるまで、join を呼び出した側のスレッドが待つという意味です。
ここでとても大切なのは、join は相手を止めるメソッドではないという点です。
join は、自分が相手の終了を待つメソッド です。
| 書き方 | 意味 |
|---|---|
| warrior1.join(); | warrior1 のスレッドが終わるまで、呼び出した側が待つ |
| warrior2.join(); | warrior2 のスレッドが終わるまで、呼び出した側が待つ |
たとえば main の中で warrior1.join() を実行した場合、待つのは main スレッドです。
warrior1 が止められるわけではありません。
warrior1 は自分の run を進めます。
main は、warrior1 の run が終わるまで待ちます。
ドラゴンボール風に言えば、BulmaSupport が GokuWarrior に「止まれ」と命令しているのではありません。
BulmaSupport が「GokuWarrior の修行が終わるまで、こちらは最終確認を待とう」としている状態です。
sleep と join の違い
sleep と join は、どちらもスレッドの流れを調整するメソッドです。
しかし、待つ理由が違います。
| メソッド | 待つ理由 | 再開のきっかけ |
|---|---|---|
| sleep | 指定した時間だけ待つ | 指定時間が過ぎる |
| join | 指定したスレッドの終了を待つ | 相手のスレッドが終わる |
つまり、次のように整理できます。
sleep は時間で待つ。
join は相手の終了で待つ。
ドラゴンボール風に言えば、sleep は「一呼吸置いてから動く」ことです。
join は「仲間の任務完了を確認してから動く」ことです。
似ているようで、目的が違います。
join で別スレッドの終了を待つ
ここでは、main スレッドが別スレッドの終了を待つ例を見ていきます。
GokuWarrior が修行記録をまとめ終わるまで、BulmaSupport が最終確認を待つイメージです。
ファイル名:Sample5.java
class WarriorTask extends Thread
{
private String name;
public WarriorTask(String nm)
{
name = nm;
}
public void run()
{
for(int i = 0; i < 5; i++) {
System.out.println(name + "が修行記録をまとめています。");
}
}
}
class Sample5
{
public static void main(String[] args)
{
WarriorTask warrior1 = new WarriorTask("GokuWarrior");
warrior1.start();
try {
warrior1.join();
}
catch(InterruptedException e) {
}
System.out.println("BulmaSupportがGokuWarriorの修行終了を確認しました。");
}
}Sample5.java の流れ
このプログラムでは、まず GokuWarrior スレッドを起動しています。
WarriorTask warrior1 = new WarriorTask("GokuWarrior");
warrior1.start();これにより、GokuWarrior スレッドの run が動き始めます。
run の中では、修行記録をまとめているメッセージを5回表示します。
public void run()
{
for(int i = 0; i < 5; i++) {
System.out.println(name + "が修行記録をまとめています。");
}
}その後、main 側では join を呼び出しています。
warrior1.join();この join によって、main スレッドは GokuWarrior スレッドが終わるまで待ちます。
そのため、次のメッセージは、GokuWarrior スレッドの run が終わったあとに表示されます。
System.out.println("BulmaSupportがGokuWarriorの修行終了を確認しました。");処理の流れを整理すると、次のようになります。
| 順番 | 処理 |
|---|---|
| 1 | main が GokuWarrior スレッドを作る |
| 2 | warrior1.start() で GokuWarrior スレッドを起動する |
| 3 | GokuWarrior スレッドが修行記録をまとめる |
| 4 | main は warrior1.join() で待つ |
| 5 | GokuWarrior スレッドの run が終わる |
| 6 | main が再開する |
| 7 | BulmaSupport が終了確認メッセージを表示する |
join は順番をそろえるために使う
複数スレッドは、本来それぞれ自由に進みます。
そのため、main が先に最後のメッセージを表示してしまう可能性もあります。
しかし、join を使うと、指定したスレッドが終わるまで待てます。
Sample5.java では、main が GokuWarrior スレッドの終了を待っています。
そのため、最後の確認メッセージは必ず GokuWarrior の修行記録整理が終わったあとに表示されます。
| join なし | join あり |
|---|---|
| main が先に終了確認を出す可能性がある | GokuWarrior スレッド終了後に確認を出せる |
| 出力順が安定しにくい | 順番をそろえやすい |
| 別スレッドの完了を待たない | 指定したスレッドの完了を待つ |
ドラゴンボール風に言えば、GokuWarrior が修行記録をまとめ終える前に、BulmaSupport が「終了確認しました」と言ってしまうと不自然です。
join を使うことで、GokuWarrior の任務完了を待ってから、BulmaSupport が最終確認を行えるようになります。
join でも InterruptedException に備える
join も InterruptedException を送出する可能性があります。
そのため、sleep と同じように try-catch で囲みます。
try {
warrior1.join();
}
catch(InterruptedException e) {
}ここでも、待機中に割り込みが起きる可能性に備えています。
学習段階では、次のように押さえておくとよいです。
| メソッド | 必要な例外処理 |
|---|---|
| sleep | InterruptedException に備える |
| join | InterruptedException に備える |
スレッドを一時停止したり、終了を待ったりする処理では、InterruptedException がよく登場します。
図:join は相手の終了を待ってから再開する

この図が示していること
この図は、main スレッドが join によって GokuWarrior スレッドの終了を待つ流れを表しています。
main は、GokuWarrior スレッドを start で起動したあと、warrior1.join() を実行します。
その時点で、main は GokuWarrior スレッドの終了を待ちます。
GokuWarrior スレッドは止められているわけではありません。
GokuWarrior スレッドは run を最後まで進めます。
そして、GokuWarrior スレッドの run が終わると、main が再開します。
| 図の要素 | 意味 |
|---|---|
| warrior1.join() | GokuWarrior スレッドの終了を待つ |
| main の待機 | join を呼び出した側が止まる |
| GokuWarrior スレッド | 自分の run を最後まで進める |
| run終了 | main が再開するきっかけ |
| 終了確認 | join のあとに実行される処理 |
この図から分かることは、join は相手を止める命令ではなく、呼び出した側が相手の終了を待つ命令だということです。
sleep と join を比べて整理する
sleep と join は、どちらもスレッドの流れを一時的に止めるように見えます。
しかし、意味は違います。
| メソッド | 止まる側 | 待つ理由 | 再開する条件 |
|---|---|---|---|
| sleep | 実行している現在のスレッド | 指定時間だけ待つため | 指定時間が過ぎる |
| join | join を呼び出した側のスレッド | 相手の終了を待つため | 相手のスレッドが終わる |
この違いはとても重要です。
たとえば、Thread.sleep(1000) は、現在のスレッドを1秒間止めます。
一方、warrior1.join() は、warrior1 が終わるまで、join を呼び出した側を待たせます。
| 書き方 | 意味 |
|---|---|
| Thread.sleep(1000); | 現在のスレッドが1秒待つ |
| warrior1.join(); | warrior1 が終わるまで、呼び出した側が待つ |
ドラゴンボール風に言えば、sleep は自分が一呼吸置くことです。
join は、仲間の任務が終わるまで自分が待つことです。
この2つを混同しないことが、スレッド制御を理解するうえで大切です。
スレッド制御が重要になる場面
スレッド制御は、複数の処理を扱うプログラムで重要になります。
特に、次のような処理では、ただスレッドを起動するだけでなく、流れを整えることが大切です。
| 場面 | 必要になる制御 |
|---|---|
| 一定間隔で処理したい | sleep |
| 処理の完了を待ちたい | join |
| 重い処理を別スレッドに分けたい | start と join |
| 表示のタイミングを調整したい | sleep |
| 最後の集計をあとで行いたい | join |
たとえば、ファイル読み込みを別スレッドで行い、その読み込みが終わったあとに集計したい場合は、join の考え方が役立ちます。
画面に1秒ごとに進行状況を出したい場合は、sleep の考え方が役立ちます。
ドラゴンボール風に言えば、任務の進行には、待つべき場面と、少し間を置くべき場面があります。
GokuWarrior が修行の型を整えるために少し止まる。
BulmaSupport が GokuWarrior の修行完了を待ってから最終確認をする。
このように、スレッド制御は複数の流れを落ち着いて扱うための基本です。
スレッド制御で押さえておきたいこと
スレッドの流れを制御するときは、次の点を押さえておくと理解しやすくなります。
| ポイント | 内容 |
|---|---|
| sleep | 現在のスレッドを一定時間だけ一時停止する |
| sleep の指定単位 | ミリ秒 |
| sleep の停止対象 | sleep を実行したスレッド自身 |
| Thread.sleep | Thread クラスのクラスメソッドとして呼び出す形 |
| join | 指定したスレッドが終わるまで待つ |
| join の待機対象 | join を呼び出した側のスレッド |
| InterruptedException | sleep や join で備える必要がある例外 |
| 制御の目的 | 複数スレッドの動きを見やすく整える |
特に大切なのは、どのスレッドが止まっているのかを見分けることです。
sleep では、現在のスレッドが止まります。
join では、join を呼び出した側が、相手の終了まで待ちます。
この違いを押さえれば、スレッド制御のコードをかなり読みやすくなります。
ドラゴンボール風に整理する
今回の内容をドラゴンボール風に整理すると、次のようになります。
| Java | ドラゴンボール風のイメージ |
|---|---|
| sleep | 戦士が一呼吸置いて動きを止める |
| Thread.sleep(1000) | 1秒だけ気を整える |
| run の中の sleep | その戦士スレッドが一時停止する |
| main の中の Thread.sleep | 本部側の main スレッドが一時停止する |
| join | 仲間の任務完了を待つ |
| warrior1.join() | GokuWarrior の任務完了まで、呼び出した側が待つ |
| InterruptedException | 待機中に割り込みが起きる可能性への備え |
スレッドは、ただ増やすだけではありません。
増えた処理の流れを、必要に応じて止めたり、待たせたり、完了を確認してから次へ進めたりできます。
sleep は、時間で待つための方法です。
join は、相手の終了で待つための方法です。
この違いが分かると、Javaのスレッドは「勝手に動く複数の処理」ではなく、「必要に応じて流れを整えられる処理」として見えてきます。
