Java入門|スレッドのしくみと基本

1本の流れでは終わらない。悟空とベジータのように、処理もそれぞれの流れで動き出す。

これまで学んできたJavaプログラムは、main()メソッドから始まって、上から下へ順番に進んでいく1本の流れとして考えることができました。
条件分岐があっても、繰り返しがあっても、たどっている流れそのものは基本的に1つでしたね。

けれども、実際のプログラムでは、1つのことだけを順番に処理していれば十分とは限りません。
たとえば、重い処理を進めながら別の表示もしたい、ある作業を続けながらほかの仕事も進めたい、という場面があります。

そんなときに登場するのがスレッドです。

スレッドは、プログラムの中にある処理の流れです。
ドラゴンボールの世界でたとえるなら、悟空が修行をしているあいだに、ベジータは別の場所で重力トレーニングを進め、ピッコロはさらに別の場所で精神統一をしているようなイメージです。
全員が同じ世界の中で動いているのに、それぞれが自分の流れで行動しています。
Javaのスレッドも、それとよく似ています。

ここでは、スレッドとは何か、どのように起動するのか、そして起動するとプログラムの流れがどう変わるのかを、ドラゴンボールの世界観に置き換えながら、ていねいに見ていきましょう。

スレッドとは何か

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

これまでの学習では、main()メソッドの中に書かれた処理を順番に実行してきました。
つまり、処理の道筋は1本でした。

スレッドを使うと、その道筋を増やすことができます。
1本しかなかった処理の流れが、2本、3本と増えていくわけです。

ドラゴンボールで考えるとわかりやすいです。

たとえば、天下一武道会の準備をするとします。
1人で全部やるなら、

  • 会場を整える
  • 出場者を呼ぶ
  • 対戦表を作る

という流れを1つずつ順番に進めることになります。

でも、悟空が出場者確認、ベジータが会場警備、ブルマが機械の準備、というように担当を分ければ、複数の作業を同時に進められます。
このそれぞれの担当の流れが、Javaでいうスレッドです。

表にすると、こんな違いになります。

状態イメージ処理の流れ
これまでのプログラム1人で順番に作業する1本
スレッドを使うプログラム複数人が別々に作業する複数本

ここで大切なのは、同時に見えるということです。
実際には環境や実行のタイミングによって細かい順序は変わりますが、少なくともプログラム側から見ると、複数の流れが独立して進んでいるように扱えます。

図で見るスレッドのイメージ

この図が示していること

この図では、1本だけの処理の流れと、複数の処理の流れの違いが視覚的に分かります。

左側は、main()の流れだけで順番に処理している普通のプログラムです。
右側は、main()に加えて悟空スレッドやベジータスレッドが動いていて、それぞれが別の仕事を進めています。

この図から分かるのは、スレッドを使うとプログラムの中に複数の担当者を用意したような状態になるということです。
1つの仕事が終わるまで待つだけでなく、別の流れでほかの処理を進めやすくなります。

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

Javaでスレッドを動かすには、まずThreadクラスをもとにしたクラスを作ります。
そして、そのクラスの中でrun()メソッドを定義します。

このrun()メソッドが、新しいスレッドで行いたい仕事のスタート地点になります。

ドラゴンボールの世界で考えると、Threadクラスは「戦士として行動するための共通の型」のようなものです。
そこから悟空用、ベジータ用の戦士クラスを作り、それぞれが何をするかをrun()に書くイメージです。

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

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

ここでの大事なポイントを表にまとめると、次のようになります。

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

特に重要なのは、run()を書くだけでは動かないということです。
動かすためには、オブジェクトを作ってからstart()メソッドを呼び出す必要があります。

Threadクラスを継承する意味

Threadクラスを継承するのは、スレッドとして動くための機能を受け取るためです。

ドラゴンボールでたとえるなら、界王様の修行法を受け継いだ戦士が、特別な訓練の流れを持てるようになる感じです。
ただの普通のクラスではなく、スレッドとして活動できる力を持ったクラスになるわけです。

この図が示していること

この図は、スレッド用のクラスがThreadクラスを土台にして作られることを表しています。

下のSaiyanWorkerは、単なる普通のクラスではありません。
Threadを継承しているので、start()で起動できる対象になります。
そして、そのクラスのrun()に書いた処理が、新しい流れとして進んでいきます。

この図から分かるのは、スレッドは突然どこからか現れるのではなく、Threadをもとにしたクラスを用意して、そこで仕事の内容を定義することで実現する、ということです。

run()メソッドの役割

run()メソッドは、新しいスレッドが始まったときに実行される処理を書く場所です。

これはとても大切です。
なぜなら、スレッドの本体はここにあるからです。

たとえば悟空スレッドなら、run()の中に

  • 修行メッセージを表示する
  • 繰り返し鍛える
  • 終わったら終了する

という流れを書きます。

ベジータスレッドなら、また別のrun()を書けます。
つまり、run()はそのスレッドの行動内容そのものです。

ここで覚えておきたいのは、run()が終わると、そのスレッドも終わるということです。
永遠に動き続けるわけではなく、run()の処理が最後まで進めば、その流れはそこで終了します。

start()メソッドの役割

スレッドを本当に動かすのは、run()ではなくstart()メソッドです。

ここは初心者が混乱しやすいところです。
run()は「何をするか」を書く場所ですが、start()は「その新しい流れを始めてください」とJavaにお願いする命令です。

イメージとしてはこうです。

メソッドイメージ
run()戦士が何をするか書いた作戦書
start()その戦士に出撃命令を出す

start()を呼び出すと、新しいスレッドが起動し、その最初の処理としてrun()の内容が実行されます。

つまり、

  1. スレッド用のオブジェクトを作る
  2. start()を呼ぶ
  3. 新しい流れでrun()が始まる

という順番になります。

サンプルプログラム:スレッドの動きを見てみよう

ここでは、ドラゴンボール風のシンプルな例で、main()の流れと別スレッドの流れが混ざって実行される様子を見てみます。

登場するクラス名は、わかりやすく SaiyanWorker にしています。
このクラスはThreadを継承し、run()の中で修行メッセージを表示します。

main()側では、別スレッドを起動したあと、自分でも別のメッセージを表示します。
すると、2つの流れの出力が交互に混ざることがあります。

ファイル名:Sample1.java

class SaiyanWorker extends Thread
{
    private String name;

    public SaiyanWorker(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)
    {
        SaiyanWorker warrior1 = new SaiyanWorker("ベジータ");
        warrior1.start();

        // main()側の流れ
        for (int i = 0; i < 5; i++) {
            System.out.println("悟空が仙豆の準備をしています。");
        }
    }
}

このプログラムの流れ

このコードは、最初はいつもどおりmain()メソッドから始まります。

まず、次の部分でスレッド用のオブジェクトを作っています。

SaiyanWorker warrior1 = new SaiyanWorker("ベジータ");

ここでは、ベジータという名前を持つSaiyanWorkerオブジェクトを作っています。
この時点では、まだ別スレッドは動いていません。
あくまで、動く準備が整っただけです。

次に、この行で新しいスレッドを起動します。

warrior1.start();

これによって、ベジータ側のrun()メソッドが別の流れとして動き始めます。
つまり、

  • ベジータが重力修行を進めています。
  • 悟空が仙豆の準備をしています。

という2種類のメッセージが、それぞれ別の流れから出力されることになります。

main()側では、そのあとすぐにfor文に入り、自分の処理を続けます。
ここがスレッドらしいところです。
ベジータ側の処理が終わるまで、main()が必ず待つわけではありません。
main()はmain()で進み、別スレッドは別スレッドで進みます。

実行結果の見え方

実行結果の一例は、次のようになります。

悟空が仙豆の準備をしています。
ベジータが重力修行を進めています。
悟空が仙豆の準備をしています。
悟空が仙豆の準備をしています。
ベジータが重力修行を進めています。
ベジータが重力修行を進めています。
悟空が仙豆の準備をしています。
ベジータが重力修行を進めています。
悟空が仙豆の準備をしています。
ベジータが重力修行を進めています。

ただし、この順番は毎回同じになるとは限りません。
実行するタイミングや環境によって、表示の混ざり方は変わります。

たとえば、ある環境では悟空のメッセージが先に多く表示されるかもしれませんし、別の環境ではベジータのメッセージが先に続くかもしれません。

ここから分かるのは、2つの処理は順番が固定されていないということです。
それぞれが独立した流れとして進んでいるので、どちらが先に少し進むかは一定ではありません。

どうして順番が決まらないのか

順番が決まらないのは、main()の流れと、新しく起動したスレッドの流れが別々に進んでいるからです。

ドラゴンボールの世界でたとえるなら、悟空が仙豆を並べている瞬間に、ベジータがちょうど重力室に入るかもしれませんし、先にベジータが数回トレーニングを終えるかもしれません。
お互いが別行動だから、細かい順番は固定されません。

Javaでも同じです。
start()を呼んだあと、新しいスレッドがいつどのくらい進むかは、実行環境の管理にゆだねられます。
そのため、出力順が入れ替わるのは自然なことです。

これはスレッドの基本を理解するうえで、とても大事な感覚です。
スレッドを使うときは、1本の流れだけを前提に考えてはいけません。
複数の流れがそれぞれ進むことを意識する必要があります。

main()のスレッドとrun()のスレッド

スレッドを学び始めたときは、新しいスレッドだけに目が向きがちですが、main()も1つのスレッドとして動いています。

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

流れ実行している内容
main()のスレッド悟空が仙豆の準備をしています。を表示する
新しく起動したスレッドベジータが重力修行を進めています。を表示する

このように考えると、スレッドを起動するというのは、もともとあったmain()の流れに加えて、新しい流れを増やすことだと分かります。

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

新しく起動したスレッドは、run()メソッドの処理が終わると終了します。
今回の例では、for文が5回終わった時点で、そのスレッドは役目を終えます。

一方で、main()側も自分のfor文が終われば、main()メソッドの処理は終わります。

つまり、それぞれの流れはそれぞれで終わります。
片方の仕事が終わったからといって、必ずしももう片方も同時に終わるとは限りません。

ドラゴンボール風にいえば、ベジータの修行が先に終わっても、悟空がまだ仙豆を並べているなら、悟空の流れはそのまま続きます。
逆に、悟空が先に終わっても、ベジータのrun()が終わるまでは、そちらの流れは続きます。

図で見るスレッドの起動

スレッドの起動を図で押さえておくと、start()とrun()の関係がより明確になります。

この図が示していること

この図では、main()の流れの途中でstart()を呼び出すと、そこから新しい流れとしてrun()が始まることを表しています。

特に分かりやすいのは、main()が止まってrun()に切り替わるのではなく、main()も続き、run()も別に始まるという点です。
ここがスレッドの核心です。

この図を見ると、スレッドの起動とは単に別のメソッドを呼ぶことではなく、別の処理の道筋を増やすことが分ります。

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

スレッドを使う利点は、時間のかかる処理を行っているあいだにも、別の処理を進められることです。

たとえば、

  • 長い計算をしながら画面表示も続けたい
  • データを読み込みながら別の作業も進めたい
  • 通信を待っているあいだにほかの処理をしたい

という場面では、スレッドの考え方がとても重要になります。

ドラゴンボールの世界でいえば、1人がずっと1つの任務に縛られるのではなく、

  • 悟空は修行
  • ベジータは測定
  • ブルマは装置の調整

のように役割を分けられるので、全体として効率がよくなります。

もちろん、スレッドが増えればそれだけ考えることも増えますが、まずは処理の流れを分けられるという基本の価値をしっかりつかむことが大切です。

ここで押さえておきたい大事なポイント

ここまでの内容で、特に大切なのは次の点です。

ポイント内容
スレッドプログラムの中の1本の処理の流れ
複数スレッド処理の流れを複数持てる状態
Threadクラスを継承スレッドとして動くクラスを作る基本
run()メソッド新しいスレッドで実行する処理を書く場所
start()メソッド新しいスレッドを起動するために呼び出す
実行順序毎回同じとは限らず、出力順は変わることがある
終了のタイミングrun()が終わればそのスレッドは終了する

スレッドの基本をドラゴンボール風に言い換えると

最後に、今回の内容をドラゴンボールの世界観でひとまとまりのイメージにしておきます。

これまでのプログラムは、悟空1人が順番に任務をこなしていくようなものでした。
それに対してスレッドを使うプログラムは、悟空が仙豆を準備しているあいだに、ベジータが重力修行を進めるような状態です。

このとき、

  • 悟空の流れ = main()のスレッド
  • ベジータの流れ = start()で起動した新しいスレッド
  • ベジータの行動内容 = run()に書いた処理

と考えると、かなり整理しやすくなります。

スレッドとは、処理を難しくするためのものではなく、1本だった流れを必要に応じて増やしていくための仕組みです。
まずは、Threadクラスを継承したクラスを作り、run()を書き、start()で起動すると、新しい処理の流れが生まれる。
この基本をしっかり押さえておけば、この先の一時停止、終了待ち、Runnable、同期といった内容にも入りやすくなります。