Java超|Threadクラスで複数処理を動かす

仲間の数だけ、処理の流れも動き出す。Threadクラスを使うと、Javaプログラムの中で複数の処理を並行して進められるようになります。

前回は、main メソッドの流れとは別に、新しいスレッドを1つ起動する方法を学びました。

main の流れが1本あり、そこに start メソッドで新しいスレッドを1本追加することで、処理の流れが2本になりました。

今回は、そこからさらに一歩進みます。

スレッドは、1つだけしか起動できないわけではありません。
Thread クラスを継承したクラスのオブジェクトを複数作り、それぞれに start メソッドを呼び出せば、複数のスレッドを動かせます。

ドラゴンボール風にたとえると、戦士本部で任務が始まったとき、1人の戦士だけが動くのではなく、複数の戦士がそれぞれ別の役割で動き出すようなものです。

たとえば、次のような任務を考えてみましょう。

流れドラゴンボール風の役割Javaでの意味
main の流れ本部側で修行場を準備する支援役最初から動いている main スレッド
1つ目のスレッドGokuWarrior が重力修行を進めるwarrior1 のスレッド
2つ目のスレッドVegetaWarrior が戦闘フォームを整えるwarrior2 のスレッド

このように、main の流れに加えて、別のスレッドを2つ起動すると、プログラムの中には少なくとも3本の処理の流れが存在することになります。

今回のポイントは、start を呼んだ回数だけ、新しい処理の流れが増える ということです。

もちろん、何でも無制限に増やせばよいわけではありません。
スレッドが増えると、処理の順番やタイミングを考える必要も出てきます。

まずは、複数のスレッドを起動すると、main 以外にも複数の run メソッドの流れが動き出す、という基本の感覚をしっかり押さえていきましょう。

スレッドをもう1つ増やすとどうなるのか

1つのスレッドを起動したときは、処理の流れは次の2本でした。

流れ内容
main のスレッド最初から動いている処理
新しく起動したスレッドstart によって増えた処理

では、さらにもう1つスレッドを起動するとどうなるでしょうか。

答えはシンプルです。

新しい処理の流れが、さらに1本増えます。

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

流れ内容
main のスレッド本部側の処理を進める
1つ目のスレッド1人目の戦士が別任務を進める
2つ目のスレッド2人目の戦士が別任務を進める

ドラゴンボール風に言えば、本部の支援役が修行場の準備をしているあいだに、GokuWarrior が重力修行を進め、VegetaWarrior が戦闘フォームを整えるような状態です。

3人が同じ世界の中で動いていますが、1人ずつ順番待ちしているわけではありません。

それぞれが自分の流れで動いています。

複数スレッドは複数の任務の流れ

スレッドを増やすということは、プログラムの中で任務担当を増やすようなものです。

1本の処理だけなら、作業は順番に進みます。

main の流れ
  ↓
修行場を準備
  ↓
重力修行
  ↓
戦闘フォーム確認

一方、複数スレッドを使うと、次のように複数の流れを持てます。

main の流れ        :修行場を準備
warrior1 の流れ    :GokuWarrior が重力修行
warrior2 の流れ    :VegetaWarrior が戦闘フォーム確認

このように、スレッドを増やすと、1本の一本道ではなく、複数の道が同時に進んでいるようなプログラムになります。

見方内容
1本の処理1人が順番に作業する
複数スレッド複数人がそれぞれの作業を進める
start を1回呼ぶ新しい流れが1本増える
start を2回呼ぶ新しい流れが2本増える

ここで大切なのは、main も止まらずに動き続けるということです。

start を呼んだからといって、main の処理が消えるわけではありません。
main は main として進み、新しく起動されたスレッドも、それぞれの run を進めます。

図:main に2本のスレッドを追加する流れ

この図が示していること

この図は、main の流れから2回 start を呼び出すことで、2本の新しいスレッドが生まれることを表しています。

main は最初から存在する処理の流れです。

そこから warrior1.start(); を呼ぶと、GokuWarrior のスレッドが動き出します。
さらに warrior2.start(); を呼ぶと、VegetaWarrior のスレッドも動き出します。

図の要素意味
mainスレッド最初から動いている処理の流れ
warrior1.start();1つ目の新しいスレッドを起動する
warrior2.start();2つ目の新しいスレッドを起動する
warrior1スレッド1つ目の run の流れ
warrior2スレッド2つ目の run の流れ

この図から分かることは、スレッドは1本だけ増やすものではなく、必要に応じて複数起動できるということです。

2つのスレッドを起動する基本の考え方

複数のスレッドを起動するといっても、考え方は難しくありません。

1つのスレッドを起動するときと同じ手順を、複数回行います。

基本は次の流れです。

手順内容
1Thread を継承したクラスを用意する
2そのクラスのオブジェクトを作る
3start を呼び出す
4必要なら別のオブジェクトを作る
5そのオブジェクトにも start を呼び出す

つまり、2つのスレッドを起動したいなら、スレッド用のオブジェクトを2つ作り、それぞれに start を呼びます。

WarriorWorker warrior1 = new WarriorWorker("GokuWarrior");
warrior1.start();

WarriorWorker warrior2 = new WarriorWorker("VegetaWarrior");
warrior2.start();

これで、warrior1 と warrior2 は、それぞれ別のスレッドとして動き始めます。

同じクラスから複数のスレッドを作れる

ここで重要なのは、同じクラスから作ったオブジェクトでも、それぞれ別のスレッドとして動ける という点です。

たとえば、WarriorWorker という1つのクラスを作っておけば、そこから複数の戦士オブジェクトを作れます。

オブジェクト名前動き
warrior1GokuWarriorGokuWarrior 用のスレッドとして動く
warrior2VegetaWarriorVegetaWarrior 用のスレッドとして動く

これは、オブジェクト指向の考え方としても大切です。

クラスは設計図です。
オブジェクトは、その設計図から作られた実体です。

ドラゴンボール風にたとえると、WarriorWorker クラスは「別任務で修行できる戦士の設計図」です。

そこから作られた warrior1 は GokuWarrior という戦士。
warrior2 は VegetaWarrior という戦士です。

同じ設計図から生まれていても、2人は別々の存在です。

それぞれに start を呼べば、それぞれが別の任務の流れを持って動き出します。

Threadクラスを継承したクラスの役割

複数スレッドでも、基本は Thread クラスの継承です。

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 + "が修行メニューを進めています。");
        }
    }
}

この WarriorWorker クラスは、Thread を継承しています。

そのため、WarriorWorker のオブジェクトは start で起動できます。

部分役割
extends Threadスレッドとして動けるクラスにする
name戦士の名前を保存する
コンストラクタオブジェクト作成時に名前を受け取る
run別スレッドで実行する修行内容を書く

このクラスを1つ用意しておけば、GokuWarrior 用、VegetaWarrior 用、さらに別の戦士用のオブジェクトも作れます。

runメソッドは各スレッドの任務内容

run メソッドには、そのスレッドで実行したい処理を書きます。

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

この run は、WarriorWorker オブジェクトごとに実行されます。

warrior1 のスレッドでは、name が GokuWarrior です。
warrior2 のスレッドでは、name が VegetaWarrior です。

そのため、同じ run のコードを使っていても、表示される名前が変わります。

スレッドname の値表示される内容
warrior1GokuWarriorGokuWarriorが修行メニューを進めています。
warrior2VegetaWarriorVegetaWarriorが修行メニューを進めています。

ドラゴンボール風に言えば、同じ修行メニューを持つ戦士クラスでも、GokuWarrior と VegetaWarrior はそれぞれ別の戦士です。

同じ作戦書を持っていても、それぞれの名前で任務報告を出します。

複数スレッドを確認する

ここでは、Thread クラスを継承した WarriorWorker クラスを使い、main 以外に2本のスレッドを起動します。

main 側では本部の支援役が修行場の準備を進めます。

その一方で、GokuWarrior と VegetaWarrior がそれぞれ別スレッドとして修行メニューを進めます。

ファイル名:Sample2.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 Sample2
{
    public static void main(String[] args)
    {
        WarriorWorker warrior1 = new WarriorWorker("GokuWarrior");
        warrior1.start();

        WarriorWorker warrior2 = new WarriorWorker("VegetaWarrior");
        warrior2.start();

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

プログラムの全体像

このプログラムには、2つのクラスがあります。

クラス役割
WarriorWorkerThread を継承したスレッド用クラス
Sample2main メソッドを持つ実行用クラス

WarriorWorker は、スレッドとして動く戦士を表すクラスです。

Sample2 は、その戦士オブジェクトを作り、start で起動するクラスです。

今回のプログラムでは、次の3本の流れが動きます。

流れ実行内容
main のスレッド支援役が修行場の準備をしています。
warrior1 のスレッドGokuWarriorが修行メニューを進めています。
warrior2 のスレッドVegetaWarriorが修行メニューを進めています。

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

1つ目のスレッドを作って起動する

main メソッドでは、まず1つ目のスレッド用オブジェクトを作っています。

WarriorWorker warrior1 = new WarriorWorker("GokuWarrior");

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

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

あくまで、スレッドとして動けるオブジェクトを作っただけです。

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

warrior1.start();

この行によって、warrior1 のスレッドが起動します。

そして、WarriorWorker クラスの run メソッドが、新しい処理の流れとして実行されます。

コード意味
new WarriorWorker("GokuWarrior")GokuWarrior 用のオブジェクトを作る
warrior1.start()GokuWarrior のスレッドを起動する
runGokuWarrior が修行メニューを進める

2つ目のスレッドを作って起動する

次に、2つ目のスレッド用オブジェクトを作っています。

WarriorWorker warrior2 = new WarriorWorker("VegetaWarrior");

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

そして、こちらにも start を呼び出します。

warrior2.start();

これで、warrior2 のスレッドも起動します。

つまり、Sample2.java では、start を2回呼び出しています。

start の呼び出し起動するスレッド
warrior1.start()GokuWarrior のスレッド
warrior2.start()VegetaWarrior のスレッド

start を2回呼んでいるので、新しいスレッドも2本生まれます。

もともとの main の流れもあるため、全体では3本の流れになります。

main側も処理を続ける

2つのスレッドを起動したあと、main 側も自分の処理を続けます。

for(int i = 0; i < 5; i++){
    System.out.println("支援役が修行場の準備をしています。");
}

ここでは、支援役が修行場を準備しているメッセージを5回表示しています。

大切なのは、main が warrior1 や warrior2 の終了を必ず待ってから進むわけではないということです。

main は main として進みます。
warrior1 は warrior1 として進みます。
warrior2 は warrior2 として進みます。

ドラゴンボール風に言えば、本部の支援役が修行場を整えているあいだに、GokuWarrior と VegetaWarrior がそれぞれ別の修行を進めている状態です。

図:同じクラスから2つのスレッドを作る流れ

この図が示していること

この図は、WarriorWorker という同じクラスから、warrior1 と warrior2 という2つのオブジェクトを作り、それぞれを別スレッドとして起動する流れを表しています。

WarriorWorker は共通の設計図です。

そこから作られた warrior1 は GokuWarrior として動きます。
warrior2 は VegetaWarrior として動きます。

図の要素意味
WarriorWorkerThread を継承した共通のクラス
warrior1GokuWarrior 用のオブジェクト
warrior2VegetaWarrior 用のオブジェクト
warrior1.start()GokuWarrior のスレッドを起動する
warrior2.start()VegetaWarrior のスレッドを起動する

この図から分かることは、クラスは1つでも、オブジェクトを複数作れば、それぞれを独立したスレッドとして動かせるということです。

実行結果の見え方

Sample2.java を実行すると、次のような結果になることがあります。

支援役が修行場の準備をしています。
GokuWarriorが修行メニューを進めています。
VegetaWarriorが修行メニューを進めています。
支援役が修行場の準備をしています。
GokuWarriorが修行メニューを進めています。
支援役が修行場の準備をしています。
VegetaWarriorが修行メニューを進めています。
GokuWarriorが修行メニューを進めています。
VegetaWarriorが修行メニューを進めています。
支援役が修行場の準備をしています。
GokuWarriorが修行メニューを進めています。
VegetaWarriorが修行メニューを進めています。
支援役が修行場の準備をしています。
GokuWarriorが修行メニューを進めています。
VegetaWarriorが修行メニューを進めています。

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

別の実行では、次のように表示されるかもしれません。

GokuWarriorが修行メニューを進めています。
GokuWarriorが修行メニューを進めています。
支援役が修行場の準備をしています。
VegetaWarriorが修行メニューを進めています。
支援役が修行場の準備をしています。
VegetaWarriorが修行メニューを進めています。
GokuWarriorが修行メニューを進めています。
支援役が修行場の準備をしています。
VegetaWarriorが修行メニューを進めています。

このように、表示の順番は固定ではありません。

GokuWarrior が先に多く表示されることもあります。
VegetaWarrior が途中で続けて表示されることもあります。
main 側の支援役の表示が先に進むこともあります。

なぜ表示順が固定されないのか

表示順が固定されない理由は、複数のスレッドがそれぞれ独立して動いているからです。

Sample2.java では、次の3本の流れがあります。

main の流れ
  支援役が修行場の準備

warrior1 の流れ
  GokuWarriorが修行メニューを進める

warrior2 の流れ
  VegetaWarriorが修行メニューを進める

これらは、1本の一本道ではありません。

複数の道があり、それぞれが自分のタイミングで進みます。

表示の見え方理由
GokuWarrior が先に表示されるwarrior1 のスレッドが先に進んだ
VegetaWarrior が先に表示されるwarrior2 のスレッドが先に進んだ
支援役が先に表示されるmain のスレッドが先に進んだ
順番が毎回違う実行のタイミングが固定されない

ドラゴンボール風に言えば、GokuWarrior、VegetaWarrior、支援役がそれぞれ別の場所で動いています。

誰の報告が先に本部へ届くかは、そのときの動き方によって変わります。

だから、表示順が毎回同じでないことは、複数スレッドでは自然な動きです。

3つの流れが存在する理由

このプログラムで3つの流れになる理由を、もう一度整理します。

main は、プログラム開始時から1つのスレッドとして動いています。

そこに、1回目の start があります。

warrior1.start();

これで、GokuWarrior のスレッドが増えます。

さらに、2回目の start があります。

warrior2.start();

これで、VegetaWarrior のスレッドも増えます。

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

きっかけ増える流れ
main の開始main のスレッド
warrior1.start()GokuWarrior のスレッド
warrior2.start()VegetaWarrior のスレッド

合計で3本です。

main
warrior1
warrior2

このように、start を呼ぶたびに、新しい処理の流れが増えていきます。

mainも1つのスレッドである

複数スレッドを考えるとき、新しく作ったスレッドだけに注目しがちです。

しかし、main も1つのスレッドです。

main メソッドは、Javaプログラムの開始地点です。

その main の処理も、1つの処理の流れとして動いています。

スレッド内容
main スレッドSample2 の main メソッドを実行する
warrior1 スレッドGokuWarrior の run を実行する
warrior2 スレッドVegetaWarrior の run を実行する

この考え方が分かると、複数スレッドの全体像が見えやすくなります。

スレッドを追加するというのは、main とは別の処理の流れを増やすことです。

main が消えるわけではありません。
main も動きます。
追加されたスレッドも動きます。

それぞれのスレッドはrunが終わると終了する

各スレッドは、自分の run メソッドが終わると終了します。

WarriorWorker の run は、for 文で5回メッセージを表示します。

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

この for 文が終わると、そのスレッドの仕事は終わります。

スレッド終了するタイミング
warrior1GokuWarrior の run が終わったとき
warrior2VegetaWarrior の run が終わったとき
mainmain の for 文が終わったとき

片方のスレッドが終わっても、他のスレッドがまだ動いていれば、その処理は続きます。

ドラゴンボール風に言えば、GokuWarrior の修行が先に終わっても、VegetaWarrior がまだ訓練中なら VegetaWarrior の流れは続きます。

支援役の準備が先に終わっても、GokuWarrior や VegetaWarrior の修行が終わるまで、それぞれの任務は続きます。

複数スレッドで気をつけたい見方

複数スレッドを扱うと、出力結果を見て「順番どおりに動いていない」と感じることがあります。

しかし、それは間違いではありません。

むしろ、複数スレッドらしい自然な動きです。

1本の流れだけなら、表示順はコードの順番にかなり近くなります。

しかし、3本の流れがある場合、次のような表示の混ざり方が起こります。

表示の混ざり方起こる理由
main の表示が続くmain が先に進んだ
GokuWarrior の表示が続くwarrior1 が先に進んだ
VegetaWarrior の表示が途中に入るwarrior2 がそのタイミングで進んだ
毎回違う順番になる実行タイミングが変わる

大切なのは、複数スレッドでは 細かな実行順序を固定して考えない ことです。

それぞれのスレッドは独立した流れとして動きます。

図:3本の流れと表示順が混ざる理由

この図が示していること

この図は、main、warrior1、warrior2 の3本の処理の流れが、それぞれ独立して進むことを表しています。

3本の流れから画面へ出力が届くため、表示順は1本の流れのように固定されません。

図の要素意味
mainスレッド支援役の準備処理
warrior1スレッドGokuWarrior の修行処理
warrior2スレッドVegetaWarrior の修行処理
画面出力3本の流れから届いた表示結果
混ざった出力複数スレッドの自然な見え方

この図から分かることは、複数スレッドでは、各処理が別々のタイミングで進むため、画面に出る順番も毎回同じとは限らないということです。

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

複数スレッドを使うと、複数の処理を並行して進められます。

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

場面複数スレッドの価値
複数の重い処理を進めたい作業を分けて進められる
通信を待ちながら別処理をしたい待ち時間に別の流れを動かせる
画面表示を止めたくない重い処理と表示処理を分けやすい
複数の担当を並行させたい処理の役割分担ができる

ドラゴンボール風に言えば、1人の戦士がすべてを抱えるのではなく、GokuWarrior は重力修行、VegetaWarrior は戦闘フォーム確認、支援役は修行場準備、通信係は本部連絡というように、任務を分担できます。

この分担によって、プログラム全体の動きに柔軟さが出ます。

ただし、複数スレッドでは順番が固定されないため、複数のスレッドが同じデータを扱う場合には注意が必要です。

今回の段階では、まず次のことを押さえれば十分です。

ポイント内容
スレッドは複数起動できるstart を複数回呼ぶと、その分だけ流れが増える
main も1つのスレッド新しいスレッドだけでなく main も動いている
同じクラスから複数作れる同じ Thread 継承クラスから複数のオブジェクトを作れる
実行順序は固定ではない出力順は環境やタイミングで変わる
run が終われば終了各スレッドは自分の run が終わると終了する

ドラゴンボール風に整理する

今回の内容をドラゴンボール風に整理すると、次のようになります。

main の流れでは、本部の支援役が修行場を準備しています。

そこに warrior1.start() によって、GokuWarrior という戦士が別の修行へ向かいます。

さらに warrior2.start() によって、VegetaWarrior という別の戦士も別の修行へ向かいます。

Javaドラゴンボール風のイメージ
main のスレッド本部側で修行場を準備する流れ
warrior1GokuWarrior という戦士オブジェクト
warrior2VegetaWarrior という戦士オブジェクト
warrior1.start()GokuWarrior への出撃命令
warrior2.start()VegetaWarrior への出撃命令
runそれぞれの修行内容
出力順が変わる報告が届く順番が毎回変わる

複数スレッドを動かすというのは、プログラムの中で複数の戦士がそれぞれの任務を進める状態を作ることです。

start を1回呼べば、新しい流れが1本増えます。
start を2回呼べば、新しい流れが2本増えます。

そして、もともとの main の流れも動いているため、今回の Sample2.java では合計3本の流れが動きます。

この感覚がつかめると、この先で学ぶ一時停止、終了待ち、Runnable インターフェイス、同期処理も理解しやすくなります。