スレッドと同期

  1. スレッド
    スレッドは、OSから独自に実行権が取得できる実行単位です。会話性を損なう長時間の処理、ネットワークなどのサーバー処理、タイマーを伴う処理などに利用されます。

  2. 複数スレッドによる共有処理
    1. 予期せぬ出来事
      複数のスレッドがあるデータを共有し、データの変更を行う場合、予期せぬ処理が起こる場合があります。例えば、複数のスレッドが次ぎのメソッドを呼び出す場合を考えます。これは、stock[]のデータを移動するだけですから、stocks[]を合計した数値は変化しないはずです。
         public void move(int from, int to, long amount)
         {
           if (stocks[from] < amount) amount=stocks[from];
           stocks[from] -= amount;
           stocks[to] += amount;
         }

      しかし、複数のスレッドでこの処理を繰り返すと、合計の値が変化することがあります。

    2. 分離できない動作
       問題は、moveの動作の中で、stocks[from] -= amount; を実行後、stocks[to] += amount;をする前に、スレッドの切り替えが起こり、処理が分断されることから起こります。例えば、スレッドAが move(2,3,100) の実行を行い、
       stocks[2] -= 100;
      を実行します。ここで、スレッドが切り替わり、スレッドBが move(3,4,80) を行います。
        stocks[3] -= 80; stocks[4] += 80;
      ここまでは問題ないですが、スレッドBが終了し、スレッドAが実行を再開します。
       stocks[to] += amount;
      を実行しますが、to と amount は中断前の値は保存されていません。再開時のtoとamountを利用しますから、この場合
       stocks[3] += 100; でなく、stocks[4] += 80;
      を実行することになります。これは、意図した動作ではありません。
       問題は、あるスレッドが move( ) 処理の実行が終了する前に、別のスレッドが move() 処理を開始することにあります。
    3. 実行の同期
       この問題を解決するため、Javaではメソッドに 同期(synchronized)を指定することができます。同期を指定されたメソッドは、一度に一つのスレッドしか実行できません。他のスレッドが実行中の同期メソッドを実行要求を出したスレッドは、実行が終了するまで待たされます。

    4. デモプロジェクト
      この、共有問題を起こすプロジェクトと、同期により解決を行うプロジェクトの例を示します。startを押すと、実行を開始します。このプロジェクトは、moveによる共有問題を起こしやすくしています。startボタンを押すと、totalの値が激しく変化します。syncのチェックを入れると、totalの変化がなくなります。
       stopで停止しますが、再開に備えて、syncのチェックをはずします。


    5. スレッドの優先順位
      スレッドには実行の優先順位を設定することが出来ます。tをスレッドとしたとき、
       t.setPriority(Thread.NORM_PRIORITY + p);
      で、設定します。Thread.NORM_PRIORITYが標準の優先度で、pだけ優先度を増減します。優先度の低いスレッドは、優先度の高いスレッドに実行権を委譲します。

  3. 問題の処理をするプロジェクト
    1. プロジェクト
      複数の倉庫にある部品の数を stocks[] に記録します。複数の運送企業がこの倉庫の間で同時に部品を移動します。
    2. プロジェクトの構成
      1. Applet1クラス
        stockクラスを生成し、startボタンで10個のMoveThreadクラスを作成します。
        また、停止ボタンでスレッドへの停止フラグを設定し、syncフラグの変化を通知します。スレッドの始動は次のように、優先度を指定しています。また、チェックボックスはスレッドが生成されていないと実行出来ないため、始動後チェックボタンを可視にしています。
          void startButton_actionPerformed(ActionEvent e) {
          int i;
          label1.setText(" ");
          stock.init();
          for (i = 0; i < 10; i++)
          {
            t[i] = new MoveThread(stock, i,1000);
            t[i].setPriority(Thread.NORM_PRIORITY + i % 2);
            t[i].start();
          }
          checkbox1.setVisible(true);
        }
        スレッドの停止は次のように行います。stopThread();で、スレッドのstopFをセットし、各スレッドで実行を停止します。この処理を行うため各スレッドをt[]で記録しています。
        void button1_actionPerformed(ActionEvent e) {
          for(int i=0;i<10;i++) {
            t[i].synchThread(false);
            t[i].stopThread();
        
          }
          checkbox1.setState(false);
          checkbox1.setVisible(false);
        }

      2. Stockクラス
        コンストラクタでstock[]を初期設定します。MoveThreadからの要求で、
         move(int th,int from, int to, long amount)
        を行い、stock[]の値を移動します。
           public  void move(int th,int from, int to, long amount)
           {
             if (stocks[from] < amount) amount=stocks[from];
             stocks[from] -= amount;
             lbl2.setText("Id:"+th+ " From:" + from+ " To: " + to + " amt:"+amount);
             stocks[to] += amount;
             nMove++;
             test();
           }
        このとき、途中にsetTextで表示を行い、他のスレッドの割り込みを起こし安くしています。一方
         public synchronized void synchmove(int th,int from, int to, long amount)
        は同じ処理を行いますが、synchronized を指定しています。synchがチェックされている場合、synchmoveを実行します。
      3. MoveThreadクラス
        Threadクラスをextendし、スレッドとして実行を行います。run()では、乱数でstocks[]のの値をランダムに移動します。synchroFフラグがセットされていれば、synchronized された処理を行います。また、stopFがセットされると、繰返しを終了します。

  4. ダウンロード
    このプロジェクトをダウンロードできます。次の行をクリックして、thread.exeファイルを適当なフォルダに保存します。 このファイルは自己解凍型の圧縮ファイルです。このファイルを実行すると同じフォルダにプロジェクトのファイルを展開します。
    ダウンロード開始