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が使われているためです。