こうしたスタイルでは、実際には、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で制御が移動しますので、先のような 出力になる訳です。
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,....という実行順序は、こうして生まれます。
真ん中の例は、どうしてそうなるのか考えて見てください。