未来エンジニア養成所Blog

プログラミングを皆に楽しんでもらうための情報をお届けします。

【Java】マルチスレッド Part1

title


マルチスレッドの利用

これまでのアプリケーションは、ある処理をしているときに同時に他の処理を行うことができません。

これはウィンドウを持ったアプリケーションなどでは問題になることがあります。

例えばデータの送受信を行っている間はウィンドウをクリックしても反応しない、などの現象が起こることがあります。

マルチスレッドとは


この原因は、“スレッド”が1つしか無いために発生します。

スレッドとは「糸」という意味があり、これは処理の流れのことを一本の糸に例えたところから由来しています。


これまで作成してきたプログラムは全て“シングルスレッド”アプリケーションといい、いうなれば1本の線で構成されたアプリケーションということです。

1本の線しかありませんので、同時に処理できるのも1つだけということです。

よって、データの送信という処理を行っている間は、他の処理を行う事が出来ないということになります。

他の処理は現在実行している処理が終わるまで、実行する事が出来ません。


これを解決する策として“マルチスレッド”があります。

マルチとは「多くの」という意味があり、いうなれば複数の糸をもつという事です。

よって、マルチスレッドアプリケーションを実現すれば、複数の処理を同時に実行する事ができ、データの転送中であっても、そのアプリケーションは他の処理を同時に実行させる事が可能になるのです。


スレッド

  • 並行処理

    • 1つのアプリケーションで、複数の処理を同時に行うこと
  • スレッドとは

    • アプリケーションが実行される際の処理の流れを表す最小単位
    • どのようなアプリケーションでも最低1つのスレッドが存在する(mainスレッド)
  • マルチスレッドとは

    • アプリケーションを、複数のスレッドに分割して実行させること
    • CPUが1つしかないマシンでは、同時に複数の処理を行うことができないので、短い時間で処理を切り替えて実行し、仮想的に複数の処理を同時に実行しているように見せかけている


Javaの1つのアプリケーションはエントリーポイントであるmain()メソッドから動作が始まることを学習してきました。


このmain()メソッドの処理がmainスレッドとなります。

マルチスレッドの場合、mainスレッドが動いている間に、別のスレッドが割り込んできます。

例えばスレッドAが動いている間はmainスレッドとその他のスレッドは動かずに待機します。

それぞれ自分のスレッドが動いている間は他のスレッドは待機状態になります。

そしてmainスレッドが終了するとすべてのスレッドは終了します。


ここで大事なポイントがあります。

どのスレッドを実行するかはOSがJavaVMに通知します。

つまりプログラマはスレッドの順番を厳密に制御できません。

スレッド


スレッドの作成と開始

Javaにはスレッドを動かすために、java.langパッケージにThreadクラス、Runnableインターフェースが用意されています。

これらのサブクラスを作ることでスレッドを作成します。


・public class Thread extends Object implements Runnable
・public interface Runnable


スレッドを作成して実行するには、次の2通りがあります。

  • Threadクラスの継承(extends)によるスレッドの作成と開始
  • Runnableインターフェースの実装(implements)によるスレッドの作成と開始


【Threadクラスの継承(extends)によるスレッドの作成と開始】

次の手順でスレッドを作成し、実行します。

  1. Threadクラスを継承したサブクラスを定義する
  2. run()メソッドを定義し、スレッドに行わせたい処理を記述する
  3. 作成したクラスをインスタンス化する
  4. インスタンス化したオブジェクトのstart()メソッドを呼び出す


【Threadクラスを継承したスレッドの作成と実行】

public class ThreadSample01 {
    public static void main(String[] args) {
        for(int i = 1; i <= 10 ; i++){
            System.out.println(i + "回目のmain()の処理です");
        }

        MyThread mt = new MyThread();
        mt.start();
    }
}
class MyThread extends Thread{
    public void run(){
        for(int i = 1; i <= 10 ; i++){
            System.out.println(i + "回目のMyThreadの処理です");
        }
    }
}


実行結果

1回目のmain()の処理です
2回目のmain()の処理です
3回目のmain()の処理です
4回目のmain()の処理です
5回目のmain()の処理です
6回目のmain()の処理です
7回目のmain()の処理です
8回目のmain()の処理です
9回目のmain()の処理です
10回目のmain()の処理です
1回目のMyThreadの処理です
2回目のMyThreadの処理です
3回目のMyThreadの処理です
4回目のMyThreadの処理です
5回目のMyThreadの処理です
6回目のMyThreadの処理です
7回目のMyThreadの処理です
8回目のMyThreadの処理です
9回目のMyThreadの処理です
10回目のMyThreadの処理です


11行目~17行目でMyTreadクラスを、Threadクラスを継承したサブクラスとして定義します。

Threadクラスにはpublic void run()メソッドが定義されており、スレッドが呼ばれたときに実行されます。

このrun()メソッドをサブクラスでオーバーライドし、スレッドで実行させたい処理を記述します。

ここでは「MyTreadの処理です」というメッセージを10回繰り返して出力する処理を記述しています。

一方、main()メソッドの処理ではmainスレッドで動く処理を記述します。

「main()の処理です」が10回繰り返し出力されるようにしておきます。

7行目~8行目では12行目~17行目で作成したスレッドクラスをインスタンス化し、そのオブジェクトでstart()を呼び出します。

ここでrun()メソッドを呼び出したいのですから、mt.run()と記述したいところですが、スレッドクラスのrun()メソッドの呼び出しはstart()で呼び出すという仕様になっているので、注意が必要です。

もしmt.run()と記述すると、スレッドクラスのrun()メソッドではなく、単なるオーバーロードされた単なるrun()メソッドが呼び出されることになります。

ThreadSample01


実行結果を見るとmain()の処理が10回繰り返された後、MyThreadの処理が10回繰り返されていますが、実行結果は必ずしもこの通りになるとは限りません。

mainスレッドを実行するか、MyThreadスレッドを実行するかはJavaVMが決めるので、タイミングによっては2つのスレッドが混じって実行されることもあります。


8行目のmt.start();をmt.run();に変更して、実行してみましょう。


実行結果

1回目のmain()の処理です
2回目のmain()の処理です
3回目のmain()の処理です
4回目のmain()の処理です
5回目のmain()の処理です
6回目のmain()の処理です
7回目のmain()の処理です
8回目のmain()の処理です
9回目のmain()の処理です
10回目のmain()の処理です
1回目のMyThreadの処理です
2回目のMyThreadの処理です
3回目のMyThreadの処理です
4回目のMyThreadの処理です
5回目のMyThreadの処理です
6回目のMyThreadの処理です
7回目のMyThreadの処理です
8回目のMyThreadの処理です
9回目のMyThreadの処理です
10回目のMyThreadの処理です


先ほどの実行結果と変わらないように見えますが、決定的な違いがあります。

先ほどの結果ではMyThreadクラスでオーバーライドしたrun()メソッドをstart()メソッドで呼び出して、マルチスレッドとして実行するので、mainスレッドとMyThreadの処理は混在する可能性があります。

一方、修正後ではrun()をmt.run()で呼び出しているので、単なるrun()を呼び出していることになり、main()スレッドの実行の順番(上から下に向かって記述された順番に実行される)に処理されるので、「main()の処理です」が10回すべて表示された後に、「MyThreadの処理です」が10回表示されることになります。


【Runnableインターフェースの実装(implements)によるスレッドの作成と開始】
次の手順でスレッドを作成し、実行します。

  1. Runnableインターフェースを実装したサブクラスを定義する。
  2. run()メソッドを定義し、スレッドに行わせたい処理を記述する。
  3. 作成したクラスをインスタンス化する。
  4. 3で作成されたオブジェクトをコンストラクタの引数に渡して、Threadクラスをインスタンス化する。
  5. 4でインスタンス化されたオブジェクトのstart()メソッドを呼び出す。


【Runnableインターフェースを実装したスレッドの作成と実行】

public class ThreadSample02 {
    public static void main(String[] args) {
        for(int i = 1; i <= 10 ; i++){
            System.out.println(i + "回目のmain()の処理です");
        }

        MyThread2 mt = new MyThread2();
        Thread th =new Thread(mt);
        th.start();
    }
}
class MyThread2 implements  Runnable{
    public void run(){
        for(int i = 1; i <= 10 ; i++){
            System.out.println(i + "回目のMyThread2の処理です");
        }
    }
}


実行結果

1回目のmain()の処理です
2回目のmain()の処理です
3回目のmain()の処理です
4回目のmain()の処理です
5回目のmain()の処理です
6回目のmain()の処理です
7回目のmain()の処理です
8回目のmain()の処理です
9回目のmain()の処理です
10回目のmain()の処理です
1回目のMyThreadの処理です
2回目のMyThreadの処理です
3回目のMyThreadの処理です
4回目のMyThreadの処理です
5回目のMyThreadの処理です
6回目のMyThreadの処理です
7回目のMyThreadの処理です
8回目のMyThreadの処理です
9回目のMyThreadの処理です
10回目のMyThreadの処理です


12行目~18行目でRunnableインターフェースを実装したMyThread2クラスを作成し、run()メソッドをオーバーライドして「MyTHread2の処理です」を10回表示させるようにしておきます。

mainスレッドでは3行目~5行目で「main()の処理です」を10回表示させるようにしておきます。

7行目のMyThread2 mt = new MyThread2();でスレッドクラスをインスタンス化しそのオブジェクトを変数mtに格納しておきます。

8行目のThread th =new Thread(mt);で、Threadクラスのコンストラクタの引数にMyThreadクラスのオブジェクトmtを指定します。

これにより作られた変数thでstart()メソッドを呼び出すと、MyThreadのrun()メソッドがマルチスレッドとして動きます。

実行結果は、JavaVMが管理しているので、毎回異なる可能性があります。

下図はJavaAPIのThreadクラスのコンストラクタの説明です。

コンストラクタの引数にRunnableをとるものがあります。

Runnableインターフェースを実装したサブクラスのオブジェクトをここに指定をすることで、Threadクラスに目的のMyThread2の参照先(ターゲット)を知らせています。

独自のスレッドクラスを、Threadクラスを継承したサブクラスとして作成した場合には、Threadクラスを継承しているので、Threadクラスに定義されたstart()を使用することが可能ですが、Runnableインターフェースを実装したサブクラスの場合には、Runnableインターフェースにstart()が定義されていないため、Threadオブジェクトを作成して、Runnableオブジェクトをターゲットとして渡しています。

Threadクラス

Threadイメージ


「マルチスレッド Part2」へ続きます。 phoeducation.work


参考図書



独学で挫折しそうになったら、オンラインプログラミングスクール
未来エンジニア養成所Logo



あわせて学習したい

phoeducation.work phoeducation.work