ASH | サーバ | セキュリティ | Linux | FreeBSD | DB | Web | CGI | Perl | Java | XML | プログラム | ネットワーク | 標準 | Tips集

マルチスレッドの同期

 C言語やPerlのfork&execのように、プロセスを同時に実行したり、同期を取るために待ち合わせる処理を行ってみましょう。 Java言語では、マルチスレッド機能が言語に含まれるため、比較的きれいにプログラムできます。
 Javaの場合、2スレッドの同期と、3スレッド以上の同期で、処理が異なります。
 マルチスレッドプログラミングも参照してください。

2スレッドの同期

 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スレッド以上の同期

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



Copyright (C)1995-2002 ASH multimedia lab.
mail : info@ash.jp