Java超|処理の流れを増やすスレッド

1本だった処理の流れを、複数の任務へ広げる。スレッドを理解すると、Javaプログラムは「順番に進む処理」から「複数の流れが並んで動く処理」へ進化します。

これまで学んできたJavaプログラムは、基本的に main メソッドから始まり、上から下へ順番に処理が進んでいくものでした。

条件分岐があっても、繰り返しがあっても、処理の流れそのものは基本的に1本です。

たとえば、次のような流れです。

順番処理
1最初の処理を実行する
2次の処理へ進む
3さらに次の処理へ進む
4最後まで実行する

このような1本の流れは、Javaの基本を理解するうえでとても大切です。

しかし、実際のプログラムでは、1つの処理だけを順番に進めればよいとは限りません。

たとえば、次のような場面があります。

やりたいこと1本の流れだけだと困ること
重い計算をしながら画面表示も続けたい計算が終わるまで画面処理が止まりやすい
ファイルを読み込みながら別の処理もしたい読み込み中に他の作業を進めにくい
通信を待ちながら操作も受け付けたい待ち時間にプログラム全体が止まったように見える
複数の作業を並行して進めたい1つずつ順番に終わるまで待つ必要がある

このようなときに登場するのが スレッド です。

スレッドとは、プログラムの中にある 処理の流れ です。

ドラゴンボール風にたとえると、戦士本部の任務で、1人の戦士がすべてを順番にこなすのではなく、複数の戦士が別々の任務を同時に進めるようなものです。

たとえば、ある任務で次のような作業が必要だとします。

作業担当
任務地の状況確認GokuWarrior
通信ラインの確認BulmaSupport
修行記録の整理GohanAnalyzer
支援装備の準備VegetaWarrior

これを1人で順番に行うと時間がかかります。

しかし、担当を分ければ、それぞれが別々の流れで作業を進められます。

Javaのスレッドも、この考え方に近いです。

プログラムの中に複数の処理の流れを作ることで、1本の処理だけでは表現しにくい動きを作れるようになります。

この記事では、スレッドとは何か、Thread クラスを継承するとはどういうことか、run メソッドと start メソッドの違い、main の流れと新しいスレッドの流れがどのように動くのかを、ドラゴンボールの世界観でやわらかく整理していきます。

スレッドとは何か

スレッドとは、プログラムの中を進む1本の処理の道筋です。

これまでの基本的なプログラムでは、main メソッドの中に書かれた処理が、上から下へ順番に実行されていました。

この場合、処理の流れは1本です。

main の流れ
  ↓
処理1
  ↓
処理2
  ↓
処理3

スレッドを使うと、この流れを増やせます。

つまり、main の流れとは別に、新しい処理の流れを作ることができます。

main の流れ
  ↓
処理A

別スレッドの流れ
  ↓
処理B

ドラゴンボール風にたとえると、1人の戦士が任務を順番にこなすだけでなく、別の戦士も同時に別任務へ向かうような状態です。

状態ドラゴンボール風のイメージ処理の流れ
スレッドなし1人の戦士が順番に作業する1本
スレッドあり複数の戦士が別々に作業する複数本

ここで大切なのは、スレッドを使うと、複数の処理が 同時に進んでいるように見える ということです。

実際の細かい実行順序は、環境やタイミングによって変わります。

しかし、プログラムの考え方としては、main の流れとは別に、新しい流れが動いていると理解できます。

1本の流れと複数の流れの違い

1本の流れでは、ひとつの処理が終わってから次へ進みます。

流れ特徴
1本の処理順番が追いやすい
1本の処理ひとつの作業が長いと、次の作業が待たされる
複数の処理別々の作業を並行して進めやすい
複数の処理実行順序が毎回同じとは限らない

ドラゴンボール風にすると、次のように考えられます。

1人の戦士だけで任務を行う場合は、次のように順番に進みます。

任務地を確認する
  ↓
通信ラインを確認する
  ↓
修行記録を整理する
  ↓
支援装備を準備する

一方、複数の戦士や支援役がいる場合は、作業を分担できます。

担当作業
GokuWarrior任務地を確認する
VegetaWarrior支援装備を準備する
GohanAnalyzer修行記録を整理する
BulmaSupport通信ラインを確認する

このように、処理の流れを増やすことで、プログラムの動き方に幅が出ます。

図:1本の処理と複数スレッドの違い

この図が示していること

この図は、1本だけの処理の流れと、複数のスレッドを使った処理の違いを表しています。

左側では、main の流れだけで、すべての作業を順番に進めています。

右側では、main の流れに加えて、GokuWarriorスレッド、BulmaSupportスレッド、GohanAnalyzerスレッドのように、複数の処理の流れが動いています。

図の要素意味
mainプログラムの最初の流れ
1本の矢印順番に処理が進む
複数のレーン複数の処理の流れ
各キャラクターアイコンそれぞれ別の仕事を進める処理

この図から分かることは、スレッドを使うと、プログラムの中に複数の担当者を用意したように、別々の処理を進められるということです。

スレッドを起動するための基本

Javaでスレッドを動かす基本的な方法のひとつは、Thread クラスを継承したクラスを作ることです。

そして、そのクラスの中に run メソッドを書きます。

基本形は次のようになります。

class クラス名 extends Thread
{
    public void run()
    {
        別スレッドで行いたい処理
    }
}

ここで大切なのは、次の3つです。

要素役割
Thread クラスを継承したクラススレッドとして動くための準備をする
run メソッド新しいスレッドで実行したい処理を書く
start メソッド新しいスレッドを起動する

ドラゴンボール風にたとえると、Thread クラスは「別任務に出られる共通の戦士型」です。

そこから、任務を担当する独自の戦士クラスを作ります。

そして、その戦士が任務で何をするかを run メソッドに書きます。

最後に start メソッドで「出撃せよ」と命じると、新しい処理の流れが始まります。

Thread クラスを継承する意味

Thread クラスを継承するのは、そのクラスをスレッドとして扱えるようにするためです。

普通のクラスは、そのままでは start メソッドで新しい処理の流れとして起動できません。

しかし、Thread を継承すると、スレッドとして動くための機能を持てます。

class クラス名 extends Thread
{
    public void run()
    {
        別スレッドで行いたい処理
    }
}

このように書くことで、WarriorWorker は Thread の機能を受け継いだクラスになります。

ドラゴンボール風に言えば、WarriorWorker は普通の記録係ではなく、「別任務として独立して動ける戦士」として扱えるようになります。

Javaドラゴンボール風のイメージ
Thread別任務に出られる共通の型
extends Threadその型を受け継いで戦士クラスを作る
run戦士が任務で行う内容
start戦士に出撃命令を出す

run メソッドの役割

run メソッドは、新しいスレッドで実行したい処理を書く場所です。

つまり、スレッドの本体です。

public void run()
{
    for(int i = 0; i < 5; i++){
        System.out.println("別スレッドの処理");
    }
}

この run メソッドに書いた処理が、新しいスレッドの流れで実行されます。

ドラゴンボール風にたとえると、run は「その戦士が任務中に何をするかを書いた作戦書」です。

たとえば、戦士スレッドなら、run の中に修行の巡回を書くことができます。

記録係スレッドなら、run の中に記録ファイルの整理を書くことができます。

run に書く内容意味
繰り返し表示するスレッドが何度も仕事をする
ファイルを読み込む別の流れで読み込みを進める
計算する重い処理を別の流れで行う
通信を待つ待機処理を別の流れに分ける

run の処理が最後まで進むと、そのスレッドは終了します。

つまり、run は新しいスレッドの始まりであり、終わりまで含む処理の流れです。

start メソッドの役割

初心者が混乱しやすいのが、run と start の違いです。

スレッドを本当に起動するのは、run ではなく start です。

メソッド役割ドラゴンボール風のイメージ
run新しいスレッドで行う処理を書く作戦書
start新しいスレッドを起動する出撃命令

run は、何をするかを書いた場所です。

start は、その処理を新しい流れとして始めるための命令です。

つまり、次のような順番になります。

手順内容
1Thread を継承したクラスを作る
2run に新しいスレッドで行う処理を書く
3そのクラスのオブジェクトを作る
4start を呼び出す
5新しいスレッドで run が実行される

ドラゴンボール風に言えば、任務内容を書いた作戦書を用意しただけでは、戦士はまだ動きません。

戦士オブジェクトを作り、start で出撃命令を出して、初めて別任務が始まります。

図:Thread を継承して start で起動する流れ

この図が示していること

この図は、Thread クラスを継承したクラスを作り、start で新しいスレッドを起動する流れを表しています。

Thread を継承することで、スレッドとして動くための機能を持ったクラスを作れます。

そのクラスの run メソッドに、別スレッドで行いたい処理を書きます。

そして、start メソッドを呼び出すことで、新しい処理の流れとして run が始まります。

図の要素意味
Threadスレッドの基本機能
extends Threadスレッドとして動けるクラスを作る
run別スレッドで行う処理を書く
start新しいスレッドを起動する
新しいスレッドmain とは別の処理の流れ

この図から分かることは、スレッドは突然生まれるのではなく、Thread をもとにしたクラスを用意し、start を呼び出すことで動き出すということです。

スレッドの動きを見る

ここでは、main の流れと、別スレッドの流れが並んで動く様子を確認します。

戦士本部の任務として、GokuWarrior が別スレッドで重力修行を進める一方で、main 側では支援役が回復カプセルの準備を進めるイメージにします。

ファイル名:Sample1.java

class WarriorWorker extends Thread
{
    private String name;

    public WarriorWorker(String nm)
    {
        name = nm;
    }

    public void run()
    {
        // 別スレッド側の修行の流れ
        for(int i = 0; i < 5; i++){
            System.out.println(name + "が重力修行を進めています。");
        }
    }
}

class Sample1
{
    public static void main(String[] args)
    {
        WarriorWorker warrior1 = new WarriorWorker("GokuWarrior");
        warrior1.start();

        // main側の流れ
        for(int i = 0; i < 5; i++){
            System.out.println("支援役が回復カプセルの準備をしています。");
        }
    }
}

このプログラムの登場人物

このプログラムには、2つのクラスが登場します。

クラス役割ドラゴンボール風のイメージ
WarriorWorkerThread を継承したスレッド用クラス別任務で動く戦士
Sample1main を持つ実行用クラス任務全体を開始する本部側

WarriorWorker は Thread を継承しています。

class WarriorWorker extends Thread

そのため、このクラスのオブジェクトは start で新しいスレッドとして起動できます。

また、WarriorWorker には name フィールドがあります。

private String name;

これは、修行する戦士の名前を保存するためのものです。

コンストラクタでは、受け取った名前を name に入れています。

public WarriorWorker(String nm)
{
    name = nm;
}

つまり、WarriorWorker オブジェクトを作るときに、どの戦士として動かすかを決められます。

run に書かれた別スレッドの処理

WarriorWorker クラスの中には、run メソッドがあります。

public void run()
{
    for(int i = 0; i < 5; i++){
        System.out.println(name + "が重力修行を進めています。");
    }
}

この run メソッドが、別スレッドとして動く処理です。

ここでは、5回繰り返して、戦士が重力修行を進めているメッセージを表示しています。

コード意味
public void run()新しいスレッドで実行する処理を書く
for(int i = 0; i < 5; i++)5回繰り返す
System.out.println修行メッセージを表示する

ドラゴンボール風に言えば、run は GokuWarrior が別任務で行う修行内容です。

この run が終わると、GokuWarrior のスレッドは終了します。

main 側の流れ

main メソッドでは、まず WarriorWorker オブジェクトを作っています。

WarriorWorker warrior1 = new WarriorWorker("GokuWarrior");

ここでは、GokuWarrior という名前を持つ WarriorWorker オブジェクトを作っています。

ただし、この時点ではまだ新しいスレッドは動いていません。

オブジェクトを作っただけです。

次に、start を呼び出しています。

warrior1.start();

ここで新しいスレッドが起動し、WarriorWorker の run メソッドが別の流れとして動き始めます。

そのあと、main 側も自分の for 文を実行します。

for(int i = 0; i < 5; i++){
    System.out.println("支援役が回復カプセルの準備をしています。");
}

つまり、このプログラムには少なくとも2本の流れがあります。

流れ実行している内容
main のスレッド支援役が回復カプセルの準備を表示する
新しく起動したスレッドGokuWarrior が重力修行を進めていると表示する

start を呼ぶと何が起きるのか

start を呼ぶと、新しいスレッドが起動します。

このとき、main の処理が run に完全に切り替わるわけではありません。

main は main で進みます。

新しいスレッドは新しいスレッドで run の処理を進めます。

ここが、普通のメソッド呼び出しとの大きな違いです。

呼び出し方動き
run を普通に呼ぶ通常のメソッド呼び出しとして、その場で順番に実行される
start を呼ぶ新しいスレッドが起動し、run が別の流れで実行される

スレッドを始めたいときは、run を直接呼ぶのではなく、start を呼ぶことが大切です。

ドラゴンボール風に言えば、作戦書 run を読むだけでは任務は増えません。

start で出撃命令を出すことで、GokuWarrior の別任務が始まります。

実行結果の見え方

このプログラムを実行すると、次のような結果になることがあります。

支援役が回復カプセルの準備をしています。
GokuWarriorが重力修行を進めています。
支援役が回復カプセルの準備をしています。
GokuWarriorが重力修行を進めています。
GokuWarriorが重力修行を進めています。
支援役が回復カプセルの準備をしています。
支援役が回復カプセルの準備をしています。
GokuWarriorが重力修行を進めています。
支援役が回復カプセルの準備をしています。
GokuWarriorが重力修行を進めています。

ただし、この順番は毎回同じになるとは限りません。

あるときは main 側のメッセージが先に多く表示されるかもしれません。

別のときは、GokuWarrior 側のメッセージが先に続くかもしれません。

これは、main のスレッドと、新しく起動したスレッドが別々に動いているからです。

なぜ表示順が毎回同じとは限らないのか

スレッドを使うと、複数の処理の流れが動きます。

そのため、どちらのスレッドが先に少し進むかは固定されません。

main の流れ
  支援役が回復カプセルを準備

別スレッドの流れ
  GokuWarriorが重力修行

この2本の流れは、それぞれ独立して進みます。

どちらが先に画面へ表示するかは、実行環境やタイミングによって変わります。

ドラゴンボール風にたとえると、支援役が回復カプセルを1つ並べる間に、GokuWarrior が重力修行を1回進めるかもしれません。

逆に、GokuWarrior が先に何度か修行を進めてから、支援役の準備が進むこともあります。

それぞれが別行動をしているため、細かい順番は毎回同じにはなりません。

理由内容
main も動いているmain の for 文も進む
別スレッドも動いているrun の for 文も進む
実行のタイミングが変わる表示順が毎回同じとは限らない
複数の流れがある1本の順番だけでは考えられない

スレッドを使うときは、1本の流れだけを前提にしないことが大切です。

main もスレッドとして動いている

スレッドを学び始めると、新しく作ったスレッドだけに注目しがちです。

しかし、main メソッドも1つのスレッドとして動いています。

つまり、Sample1.java では、少なくとも次の2つのスレッドがあります。

スレッド内容
main のスレッド支援役が回復カプセルの準備を表示する
WarriorWorker のスレッドGokuWarrior が重力修行を表示する

スレッドを起動するということは、もともとある main の流れに、新しい流れを追加することです。

ドラゴンボール風に言えば、本部側の支援役が準備を進めている間に、GokuWarrior という戦士が別の修行へ出るようなものです。

main が消えるわけではありません。

main も動き続けます。

その横で、新しいスレッドも動きます。

スレッドはいつ終了するのか

新しく起動したスレッドは、run メソッドの処理が終わると終了します。

今回の例では、run の中にある for 文が5回終わると、GokuWarrior のスレッドは終了します。

for(int i = 0; i < 5; i++){
    System.out.println(name + "が重力修行を進めています。");
}

一方、main 側も、自分の for 文が5回終わると、main メソッドの処理を終えます。

for(int i = 0; i < 5; i++){
    System.out.println("支援役が回復カプセルの準備をしています。");
}

それぞれの流れは、それぞれの仕事が終われば終了します。

流れ終了するタイミング
main のスレッドmain の for 文が終わる
新しいスレッドrun の for 文が終わる

片方が先に終わったからといって、もう片方も必ず同時に終わるわけではありません。

ドラゴンボール風に言えば、GokuWarrior の重力修行が先に終わっても、支援役がまだ回復カプセルを並べていれば、その準備は続きます。

逆に、支援役の準備が先に終わっても、GokuWarrior の run が終わるまでは、修行の流れは続きます。

図:main と別スレッドが並んで進む流れ

この図が示していること

この図は、main の流れと、新しく起動したスレッドの流れが並んで進む様子を表しています。

main の中で warrior1.start(); を呼び出すと、新しいスレッドが起動し、run の処理が始まります。

しかし、main が止まって run だけになるわけではありません。

main もそのまま続きます。

図の要素意味
main スレッド最初から動いている処理の流れ
warrior1.start()新しいスレッドを起動する命令
run新しいスレッドで実行される処理
2本のレーンmain と別スレッドが別々に進む
表示順は固定されないどちらが先に進むかは毎回同じとは限らない

この図から分かることは、スレッドの起動とは、単に別のメソッドへ移動することではなく、新しい処理の道筋を増やすことだということです。

スレッドを使うと何がうれしいのか

スレッドを使う利点は、時間のかかる処理を進めながら、別の処理も進められることです。

たとえば、次のような場面でスレッドの考え方が役立ちます。

場面スレッドが役立つ理由
長い計算をする計算中にも別の処理を進めやすい
ファイルを読み込む読み込み中に画面表示などを続けやすい
通信を待つ待機中に他の処理を進めやすい
複数の作業を同時に進めたい処理の担当を分けられる

ドラゴンボール風に言えば、1人の戦士がすべてを担当するのではなく、戦士、支援役、記録係、通信係がそれぞれの役割を持って動くことで、任務全体が進めやすくなるようなものです。

ただし、スレッドは便利な反面、考えることも増えます。

特に、複数のスレッドが同じデータを扱う場合は、順番やタイミングに注意が必要になります。

今回の記事では、まず次の基本を押さえることが大切です。

基本内容
スレッドプログラムの中の処理の流れ
複数スレッド処理の流れを複数持つこと
Thread を継承スレッドとして動くクラスを作る
run新しいスレッドで行う処理を書く
start新しいスレッドを起動する
実行順序毎回同じとは限らない
終了run が終わると、そのスレッドも終わる

run を直接呼ぶ場合との違い

スレッドを学ぶときに、run を直接呼ぶのと start を呼ぶのはどう違うのかが気になるかもしれません。

run を直接呼ぶと、それは普通のメソッド呼び出しです。

つまり、新しい処理の流れは生まれません。

warrior1.run();

この場合、main の流れの中で run の処理が順番に実行されます。

一方、start を呼ぶと、新しいスレッドが作られ、その新しい流れで run が実行されます。

warrior1.start();

違いを整理すると、次のようになります。

呼び出し方新しいスレッド処理の流れ
run を直接呼ぶ作られないmain の流れで普通に実行される
start を呼ぶ作られるrun が別の流れで実行される

ドラゴンボール風に言えば、run を直接呼ぶのは、戦士に作戦書をその場で読み上げさせるだけです。

start を呼ぶのは、戦士を実際に別任務へ出すことです。

スレッドとして動かしたいなら、start を使うことが重要です。

処理の流れが増えるという感覚

スレッドの基本でいちばん大切なのは、処理の流れが増えるという感覚です。

これまでのプログラムでは、main の1本の流れを追えば、だいたい実行順序を考えられました。

しかし、スレッドを使うと、main とは別の流れが生まれます。

そのため、出力順が固定されないことがあります。

ここで大切なのは、出力順が変わることを異常だと思わないことです。

むしろ、それがスレッドらしい動きです。

見え方理由
メッセージが交互に表示されるmain と別スレッドが両方進んでいる
片方が先に多く表示されるそのスレッドが先に多く実行された
毎回順番が違う実行タイミングが固定されない
run が終わると表示が止まるそのスレッドの処理が終了した

ドラゴンボール風に言えば、複数の戦士が同じ本部内で別々の任務を進めているため、誰の報告が先に届くかは毎回同じとは限らないということです。

スレッドの基本をドラゴンボール風に整理する

これまでのプログラムは、1人の戦士が順番に任務をこなしていくようなものでした。

スレッドを使うプログラムは、支援役が回復カプセルを準備しているあいだに、GokuWarrior が別の場所で重力修行を進めるようなものです。

Javaドラゴンボール風のイメージ
main のスレッド本部側で支援作業を進める流れ
新しいスレッド別任務へ出る戦士の流れ
Thread を継承したクラス別任務に出られる戦士クラス
run戦士が行う任務内容
start戦士への出撃命令
出力順が変わる複数の報告が別々のタイミングで届く
run の終了その戦士の任務完了

スレッドとは、処理を難しくするためのものではありません。

1本だった処理の流れを、必要に応じて増やすためのしくみです。

まずは、Thread を継承したクラスを作る。
run に別スレッドで実行したい処理を書く。
オブジェクトを作る。
start を呼ぶ。
すると、main とは別の処理の流れが始まる。

この基本を押さえておくと、この先で学ぶ一時停止、終了待ち、Runnable、同期といった内容にも進みやすくなります。