スレッド動作の詳解(5/7)

ここでは、スレッドの動きを少し詳しくみていきます。

  1. スレッドについて
  2. スレッドの実装方法について
  3. スレッドの同期について(1/2)
  4. スレッドの同期について(2/2)
  5. スレッドの処理順制御について
  6. スレッドの同時実行可否について
  7. デッドロックについて

5. スレッドの処理順制御について

前回のサンプルプログラムにおいて、現実世界ではありえない事象が発生していました。それは、一時的?にマイナスのガソリン量を保持してしまうという事象です。

今回は、その事象を解消するために、、以下の2点を仕様に盛り込みたいと思います。


  1. ガソリンスタンドへのガソリン補給は、ガソリン残量が0Lの場合のみ行う。それ以外の場合は、待機する。
  2. 車へのガソリン補給(消費)は、ガソリン残量が1L以上の場合のみ行う。それ以外の場合は、待機する。

以下の仕様、サンプルコードで具体例を示します。

※なお、赤字の箇所については、「4. スレッドの同期について(2/2)」からの追加・変更点を表しています。

<<サンプルコードの仕様説明>>> ※仕様追加・変更箇所は赤字で記述
  ・RacingCarクラス(スレッド処理)
    車自身です。
    共有のガソリンスタンド(GasStandクラス)から1周毎に1Lのガソリンを補給します。
    例では、サーキット5周を走行します。

  ・CarRaceクラス
    車(RacingCarクラス)の初期設定、及びスタートを行います。
    ガソリン補給(Replenishmentクラス)の初期設定、及び補給開始を行います。
    ガソリンスタンド(GasStandクラス)の初期設定(ガソリンの初期量)、残量の表示を行います。
    ※最後の残量表示は、全スレッドの終了(joinメソッド)を待って表示します。
    例では、車を3体用意し、各々が別スレッドで処理を行い、順位を競わせます。

  ・GasStandクラス
    ガソリンを、1L単位で車に供給します。
    (ただし、ガソリン残量が0Lの場合は待機します。)
    ガソリンを補給してもらいます。
    (ただし、ガソリン残量が1L以上の場合は待機します。)

    ・Replenishmentクラス(スレッド処理)※新規追加
    ガソリンスタンド(GasStandクラス)にガソリン補給を行います。
    CarRaceクラスの仕様より15回(1回に1L)補給します。

5-1. 複数スレッドの処理順を制御する場合

■サンプルコード

5-1-1. RacingCar.java

  「4-1-1. RacingCar.java」と同じ

5-1-2. CarRace.java

  「4-1-2. CarRace.java」と同じ


5-1-3. GasStand.java ※赤字の箇所は、「4-2-3.GasStand.java」からの追加・修正点

class GasStand{
    private int totalGas;

    public GasStand(int gas){
        this.totalGas = gas;
    }

    public synchronized void getGas(){
        int tmpGas = this.totalGas;
        //ガソリン総量が0Lの間はループ&wait待機(0L以外の場合、車補給処理)
        while(tmpGas == 0){
            try {
                wait();
            }catch(InterruptedException e){
                e.printStackTrace();
            }
            //最新のガソリン総量を取得
            tmpGas = this.totalGas;
        }

        try{
            //補給時間0.5秒スリープ
            Thread.sleep(500);
        }catch(InterruptedException e){
            e.printStackTrace();
        }
        this.totalGas = tmpGas - 1;
        //全待機スレッドを実行可能状態へ
        notifyAll();
    }

    public synchronized void putGas(){
        int tmpGas = this.totalGas;
        //ガソリン総量が0L以外の間はループ&wait待機(0Lの場合、スタンド補給処理)
        while(tmpGas != 0){
            try {
                wait();
            }catch(InterruptedException e){
                e.printStackTrace();
            }
              //最新のガソリン総量を取得
              tmpGas = this.totalGas;
        }

        try{
            //補給時間0.25秒スリープ
            Thread.sleep(250);
        }catch(InterruptedException e){
            e.printStackTrace();
        }
        this.totalGas = tmpGas + 1;
        //全待機スレッドを実行可能状態へ
        notifyAll();
    }

    public void printTotalGas(){
        System.out.println("\n■ガソリンスタンド■ ガス総量"+this.totalGas+"L\n");
    }
}

5-1-4. Replenishment.java

  「4-1-4. Replenishment.java」と同じ


■処理イメージ

今回のサンプルプログラムで行っている処理のイメージを下図で示します。

またwaitメソッド、notifyAllメソッドをという新しいメソッドをについて先に説明しておきたいと思います。


・waitメソッドについて
既に排他的に実行状態にあるスレッドが、実行待機状態にあるスレッドに実行権を(一時的)譲るのに使用します。 waitメソッドを実行したスレッドは、waitセットに入り、notifyAll(notify)メソッドにより呼び起こされるまで睡眠状態になります。 また、そのスレッドが保持していたインスタンスのロックは、waitセットに入った時点で解除されます。
※waitセットとは、仮想的な概念で、インスタンスごとに用意されているスレッドの待合室みたいなもののことを言います。

・notifyAllメソッドについて
既に排他的に実行状態にあるスレッドが、waitセットにて睡眠している全スレッドを呼び起こし、実行待機状態にするのに使用します。 実際に、実行待機状態にあるスレッドが、実行状態になるタイミングとしては、排他的に実行状態にあるスレッドが、notifyAllメソッドを実行した時ではなく、ロックを解除した時になります。
※同じ様な機能のメソッドにnotifyメソッドがあります。  このメソッドはwaitセットにて睡眠しているスレッドを1つだけ呼び起こします。

・wait・notifyAll・notifyメソッド使用時の注意点
これらのメソッドは、以下の箇所に記述しないと使えないので注意してください。
  • synchronizedブロックの中
  • synchronizedメソッドの中
  • synchronizedブロック・メソッドから呼び出される別のメソッドの中

スレッド処理イメージ1 スレッド処理イメージ2 スレッド処理イメージ3 スレッド処理イメージ4 スレッド処理イメージ5 スレッド処理イメージ6 スレッド処理イメージ7

■実行結果イメージ

■ガソリンスタンド■ ガス総量0L

***** 補給 第1回目1L
                        2号車 1周目通過
***** 補給 第2回目1L
                                                3号車 1周目通過
***** 補給 第3回目1L
1号車 1周目通過
***** 補給 第4回目1L
                                                3号車 2周目通過
***** 補給 第5回目1L
                        2号車 2周目通過
***** 補給 第6回目1L
1号車 2周目通過
***** 補給 第7回目1L
                        2号車 3周目通過
***** 補給 第8回目1L
1号車 3周目通過
***** 補給 第9回目1L
                                                3号車 3周目通過
***** 補給 第10回目1L
1号車 4周目通過
***** 補給 第11回目1L
                        2号車 4周目通過
***** 補給 第12回目1L
1号車 5周目通過
1号車 GOAL!!
***** 補給 第13回目1L
                                                3号車 4周目通過
***** 補給 第14回目1L
                        2号車 5周目通過
                        2号車 GOAL!!
***** 補給 第15回目1L
                                                3号車 5周目通過
                                                3号車 GOAL!!

■ガソリンスタンド■ ガス総量0L
        

ガソリン補給スレッドが1L補給を行う毎に、車スレッドが1L消費し処理順の制御がうまく行われています。

今回のサンプルプログラムだと、時点時点でのガソリン残量がスイッチになり、残量0LならputGasメソッドのスイッチがONになり1L補給、はたまたそれ(残量1L)がgetGasメソッドのスイッチになり1L消費され、という風に処理が切り替え切り替えで処理順の制御を行っています。

▲PageTop


  1. スレッドについて
  2. スレッドの実装方法について
  3. スレッドの同期について(1/2)
  4. スレッドの同期について(2/2)
  5. スレッドの処理順制御について
  6. スレッドの同時実行可否について
  7. デッドロックについて