next up previous contents
Next: Threadのステータス Up: Thread Previous: Thread Priority

MultiThreaded Programs

こうしたスタイルでは、実際には、sequential な、実行と大差がないと感じた人が いるかも知れません。確かに、そうです。

ところが、三つのスレッドに、均等に仕事をしてもらう方法があるのです。 次の例を見てください。優先順位を、全て1にして同一にした他は、たった一か所だけ、 yield()というメソッドの呼び出しが追加されているだけなのですが、出力は、大きく 変わります。

class Sample4 extends Thread {
    static int loop = 3 ;
    static int priority[] = { 1, 1, 1 };

    public void run() {
        for( int i=1 ; i < 5  ; i++ ) {
            System.out.println( getName() +  ": " +  i + "; " );
            yield();
        }
    }

    public static void main(String args[]) {
        for (int i = 0; i < loop ; i++) {
            Sample4 s = new Sample4();
            s.setPriority(priority[i]);
            s.start();
        }
    }
}

        Thread-4: 1;
        Thread-5: 1;
        Thread-6: 1;
        Thread-4: 2;
        Thread-5: 2;
        Thread-6: 2;
        Thread-4: 3;
        Thread-5: 3;
        Thread-6: 3;
        Thread-4: 4;
        Thread-5: 4;
        Thread-6: 4;

同一順位でのスレッドの実行は、先に説明したように、一つ一つのスレッドの終了 を待って、順番に行われるのですが、メソッド yieldは、このメソッドを呼び出した スレッドの実行を放棄して、順番を待つ次のメソッドに制御を移します。 この例の場合では、スレッド内で System.out.println()が呼び出される度に、 yield()が呼ばれて、制御が次のスレッドに渡ることになります。

いまの場合には、3つのスレッドのプライオリティが一緒でしたが、ちょっと違う 場合にはどうなるかを見てみることにしましょう。

static int priority[] = 1, 2, 2 ;

とした場合の出力は、次のようになります。

        Thread-5: 1;
        Thread-6: 1;
        Thread-5: 2;
        Thread-6: 2;
        Thread-5: 3;
        Thread-6: 3;
        Thread-5: 4;
        Thread-6: 4;
        Thread-4: 1;
        Thread-4: 2;
        Thread-4: 3;
        Thread-4: 4;

最初のスレッドのプライオリティが1で、低いのですが、残りのスレッドの 優先順位は同じです。ですから、優先順位の高い二つのスレッドが先に 実行されます。ただし、println毎に、yeildで制御が移動しますので、先のような 出力になる訳です。

waitの影響

yield を使わなくても、並列実行を行わせる方法があります。 次のプログラムを見てください。 先のプログラムとの一番の違いは、yeild()のあったところに、sleep()が呼ばれている ことであることに注意してみてください。各スレッドは、実行後、引数に渡された 値のミリ秒の間、実行を休みます。プライオリティの設定は、先の例と同じく、最初 のスレッドが一番低く、2番目、3番目のスレッドが同じ高さというようになっています。

class Sample5 extends Thread {
    static int loop = 3 ;
    static int priority[] = { 1, 2, 2 };

    long    waitTime ;
    Sample5(String time){
        waitTime=Long.valueOf(time).longValue();
    }

    public void run() {
        for( int i=1 ; i < 5  ; i++ ) {
            System.out.println( getName() +  ": " +  i + "; " );
            try{
               Thread.sleep( waitTime );
            } catch (Exception e ){
               System.err.println(e);
            }
        }
    }

    public static void main(String args[]) {
        for (int i = 0; i < loop ; i++) {
            Sample5 s = new Sample5(args[0]);
            s.setPriority(priority[i]);
            s.start();
        }
    }
}

この例が少し面白いのは、引数を変えると、すなわちsleepの間隔を変えると、 実行順序が、微妙に変化してくることです。実際に、sleepの間隔を、100ミリ秒・ 10ミリ秒・1ミリ秒と変化させた時の変化の例を見てみましょう。

        96 ews1 maru> java Sample5 100
        Thread-5: 1;
        Thread-6: 1;
        Thread-4: 1;
        Thread-5: 2;
        Thread-6: 2;
        Thread-4: 2;
        Thread-5: 3;
        Thread-6: 3;
        Thread-4: 3;
        Thread-5: 4;
        Thread-6: 4;
        Thread-4: 4;

        97 ews1 maru> java Sample5 10
        Thread-5: 1;
        Thread-6: 1;
        Thread-4: 1;
        Thread-5: 2;
        Thread-6: 2;
        Thread-4: 2;
        Thread-5: 3;
        Thread-6: 3;
        Thread-4: 3;
        Thread-5: 4;
        Thread-6: 4;
        Thread-4: 4;

        98 ews1 maru> java Sample5 1
        Thread-5: 1;
        Thread-6: 1;
        Thread-5: 2;
        Thread-6: 2;
        Thread-5: 3;
        Thread-6: 3;
        Thread-5: 4;
        Thread-6: 4;
        Thread-4: 1;
        Thread-4: 2;
        Thread-4: 3;
        Thread-4: 4;

ここでは、最後の出力例が、先のyeild()で行った出力と同じことを確認してください。 また、最初の出力例が、三つのスレッドのプライオリティの差が無い場合の出力によく 似ていることも注意してください。(4,5,6,4,5,6...という順序ではなく、5,6,4, 5,6,4,....という順序なのが違いです。) 真ん中の、出力例は、これらの中間の 形です。

こうした違いは、次のように考えれば、ある程度の理解がつきます。

まず、最後の例ですが、sleepの時間が非常に短いので、sleepの実行は、制御が他に 移るという意味しか持ちません。制御の移動は、メソッドyieldの行う最も基本的な 機能です。

最初の例ですが、Javaは、まず、一番優先順位の高いスレッドを実行しようとします。 続いてsleepが呼び出され、sleepの実行は、直接には、制御の移動を引き起こします。 所が、最初に実行されたスレッドも、それに引き続いて実行される同一順位の プライオリティのスレッドも、100ミリ秒ほど眠りに入ります。眠っているスレッド を起こすことが出来るのは、時間の経過だけです。Javaは、活動を止めている、 先順位の高かった二つのスレッドの代わりに、今度は、優先順位の低いスレッドの 実行を開始します。、5,6,4,5,6,4,....という実行順序は、こうして生まれます。

真ん中の例は、どうしてそうなるのか考えて見てください。



maruyama@wakhok.ac.jp