C言語やPerlのfork&execのように、プロセスを同時に実行したり、同期を取るために待ち合わせる処理を行ってみましょう。
Java言語では、マルチスレッド機能が言語に含まれるため、比較的きれいにプログラムできます。
Javaの場合、2スレッドの同期と、3スレッド以上の同期で、処理が異なります。
マルチスレッドプログラミングも参照してください。
2つのスレッド間の同期は、joinメソッドを使って、より簡単に実現できます。
子スレッドを起動し並行処理を行い、親スレッドが子スレッドの終了を待って、次の処理を行うプログラムを作成してみます。
処理内容は、Hello World!を表示するだけですが、スレッド名も表示するようにしています。
各スレッドの処理の流れは、以下のようになります。
親スレッドは、joinメソッドで、子スレッドが終了するまで待ちます。
親スレッド 子スレッド ↓main ↓ ↓ Thread-0 □→→→→→→→→□起動 ↓ ↓ ↓ 同時実行 ↓ ↓join ↓ □←←←←←←←←□終了 ↓ ↓ 子が終了後に実行 ↓ |
以下のソースを入力してください。
HelloFork.java |
---|
public class HelloFork extends Thread { int pause; public static void main(String args[]) { HelloFork th; // スレッドの起動 th = new HelloFork(1000); th.start(); // 親スレッドの処理(子スレッドと同時実行) System.out.println("Hello World! at " + Thread.currentThread().getName()); // 子の終了を待つ try { th.join(); } catch(Exception ex) { ex.printStackTrace(); } // 親スレッドの処理(子スレッド終了後に実行) System.out.println("Hello World! at " + Thread.currentThread().getName()); } public HelloFork(int p) { // 子スレッドの初期設定処理 pause = p; } public void run() { // 子スレッドの実行処理 try { sleep(pause); } catch (Exception ex) { } System.out.println("Hello World! at " + Thread.currentThread().getName()); } } |
ソースをコンパイルして実行してみましょう。 Hello World!とスレッド名が表示されますので、mainとThread-0が同時に実行され、Thread-0が終了後に、mainの処理が続くことが確認できます。
unix# javac HelloFork.java unix# java HelloFork Hello World! at main Hello World! at Thread-0 Hello World! at main |
3スレッド以上の間での同期には、syncronized機能を使う必要があります。
このプログラムでは、3つの子スレッドを起動し、親と3つの子スレッドで並行処理を行い、親がすべての子の終了を待って、次の処理を行います。
処理内容は、Hello World!を表示するだけですが、スレッド名も表示するようにしています。
各スレッドの処理の流れは、以下のようになります。
親スレッドは、waitメソッドで、すべての子スレッドからnotifyメソッドが呼ばれるまで待ちます。
親スレッド 子スレッド1 子スレッド2 子スレッド3 ↓main ↓Thread-0 ↓ Thread-1 □→→→→→→→→□起動 Thread-2 □→→→→→→→→+→→→→→→→→□起動 Thread-3 □→→→→→→→→+→→→→→→→→+→→→→→→→→□起動 ↓ ↓ ↓ ↓ ↓ 同時実行 ↓ 同時実行 ↓ 同時実行 ↓ ↓wait ↓notify ↓ ↓ □←←←←←←←←□終了 ↓notify ↓ □←←←←←←←←←←←←←←←←←□終了 ↓notify □←←←←←←←←←←←←←←←←←←←←←←←←←←□終了 ↓ ↓ すべての子が終了後に実行 ↓ |
以下のソースを入力してください。
同期処理としては、addSync(), delSync(), waitSync()のメソッドがあります。
子スレッドを1つ作成する前にaddSyncを呼び出し、子スレッドを終了した後でdelSyncを呼び出します。
delSyncではnotifyメソッドを呼び出します。
すべての子スレッドが終了するまで待つ場合には、waitSyncメソッドを呼び出します。
HelloSync.java |
---|
public class HelloSync extends Thread { int cnt; // アクティブスレッド数 public static void main(String args[]) { int num = 3; HelloSyncChild ths[]; HelloSync sync; int i; // 同期オブジェクトの作成 sync = new HelloSync(); // 子スレッド情報配列の作成 ths = new HelloSyncChild[num]; // スレッドの起動 for (i = 0; i < num; i++) { ths[i] = new HelloSyncChild(sync, (i+1)*1000); ths[i].start(); } // 親スレッドの処理(子スレッドと同時実行) System.out.println("Hello World! at " + Thread.currentThread().getName()); // 子の終了を待つ sync.waitSync(); // 親スレッドの処理(子スレッド終了後に実行) System.out.println("Hello World! at " + Thread.currentThread().getName()); } public HelloSync() { // 同期オブジェクトの初期化処理 cnt = 0; } public synchronized void addSync() { // 同期オブジェクトの追加 cnt++; } public synchronized void delSync() { // 同期オブジェクトの削除 cnt--; try { notify(); } catch (Exception ex) { ex.printStackTrace(); } } public synchronized void waitSync() { // 同期オブジェクトの待ち合わせ(子がすべて終了するまで待つ) while (cnt > 0) { try { wait(); } catch (Exception ex) { ex.printStackTrace(); } } } } |
子スレッドでの処理は別のソースで記述します。
以下のソースを入力してください。
子スレッドでは、同期スレッドと、パラメータを受け取ります。
同期スレッドは、終了時にnotifyを発行するために必要です。
HelloSyncChild.java |
---|
public class HelloSyncChild extends Thread { HelloSync sync; int pause; public HelloSyncChild(HelloSync s, int p) { // 子スレッドの初期設定処理 sync = s; pause = p; sync.addSync(); } public void run() { // 子スレッドの実行処理 try { Thread.sleep(pause); } catch (Exception ex) { ex.printStackTrace(); } System.out.println("Hello World! at " + Thread.currentThread().getName()); sync.delSync(); } } |
ソースをコンパイルして実行してみましょう。 Hello World!とスレッド名が表示されますので、mainと、Thread-1、Thread-2、Thread-3、が同時に実行され、すべてのスレッドが終了後に、mainの処理が続くことが確認できます。
unix# javac HelloSync.java HelloSyncChild.java unix# java HelloSync Hello World! at main Hello World! at Thread-1 Hello World! at Thread-2 Hello World! at Thread-3 Hello World! at main |
ここで、Thread-0が表示されないのは、同期用スレッドとして、Thread-0が使われているためです。