wait()/notify() (2)

Revised: May/2nd/2002; Since: Apr./28th/2002

ここではシナリオに基づいて wait()/notify() を実装したプログラムを作ってみましょう。

シナリオ

機能概要

Warehouse 型の共有オブジェクトを Consumer スレッドと Producer スレッドが利用します。

Producer スレッドが値をセットするのを待って Consumer スレッドが値を取得し、逆に Consumer スレッドが値を取得し終わるのを待って Producer スレッドが新しい値をセットします。

この実行と再開のタイミングがずれると、値がセットされる前に取得してその後の実行に問題が生じたり、値が取得される前に新しい値がセットされて古い値が処理されることなく上書きされてしまったりします。

Warehouse

共有オブジェクト。

put()
msg に値をセットし、 statetrue をセット。
get()
msg の値を返し、 statefalse をセット。

Consumer

共有オブジェクトから値を取り出すスレッド。一つの Warehouse 型共有オブジェクトに対して、複数の Consumer 型オブジェクトが存在するかもしれない。

run()
Warehouse オブジェクトの get() を呼び出す。

Producer

共有オブジェクトに値をセットするスレッド。一つの Warehouse 型共有オブジェクトに対して、複数の Producer 型オブジェクトが存在するかもしれない。

run()
Warehouse オブジェクトの put() を呼び出す。

同期

このプログラムでは Consumer オブジェクトと Producer オブジェクトが共有オブジェクトに所定の順番で排他的にアクセスする必要がありますので、共有オブジェクトに対する同期が必要です。

このシナリオで最善の方法は、共有オブジェクト側で自分を利用するオブジェクトの実行を停止させたり実行許可を与えたりすることです。

実行の待機、再開許可は wait() メソッド、 notify()/notifyAll() メソッドを利用します。これらのメソッドは Object クラスで定義されており、任意のクラスで使うことが出来ますが、 synchronized で同期指定されたコードの内部でなければ使えません。

サンプル

共有オブジェクト: Warehouse

共有オブジェクト Warehouse のメンバーは次の4つです。

int msg
共有されるデータを保持する変数です。
boolean state
msg が読み込まれて新しい値が書き込み可能になれば false になり、新しい値がセットされて読み込み可能になれば true になります。
void put(int val)
statetrue であればロックを取得しているスレッドを待機させ、 false になればメソッド引数を msg にセットします。このとき statetrue に戻します。
int get()
statefalse であればロックを取得しているスレッドを待機させ、 true になれば msg の値を返します。このとき statefalse に戻します。
class Warehouse {
	private int msg;
	private boolean state = false;
	
	public synchronized void put(int val) {
		while (state == true) {
		// 値がセットされていれば繰り返し
		// 読み込まれれば繰り返し終了
			try {
				wait();	// msg に値がセットされていれば待機
			} catch (InterruptedException e) {
				System.out.println(e);
			}
		}

		msg = val;
		state = true;	// msg に値がセットされたら true
		notifyAll();
	}
	
	public synchronized int get() {
		while (state == false) {
		// 読み込み済みならば繰り返し
		// 新しい値がセットされれば繰り返し終了
			try {
				wait();	// msg の値を読み込み済みならば待機
			} catch (InterruptedException e) {
				System.out.println(e);
			}
		}

		state = false;	// msg の値を読み込んだら false
		notifyAll();
		return msg;
	}
}

Producer

Warehouse 型オブジェクトに値をセットするスレッド・クラスです。

class Producer implements Runnable {
	// 共有オブジェクト
	private Warehouse msg;
	
	// コンストラクタ
	Producer(Warehouse obj) {
		msg = obj;
	}
	// スレッドの run() メソッド
	public void run() {
		// 共有オブジェクトに値を5回セット
		for (int i = 0; i < 5; i++) {
			msg.put(i);
			System.out.println("Producer: " + i);
			
			// 作為的にタイミングをずらすスリープ
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				System.out.println(e);
			}
		}
	}
}

Consumer

Warehouse 型オブジェクトの値をゲットするスレッド・クラスです。

class Consumer implements Runnable {
	// 共有オブジェクト
	private Warehouse msg;
	
	// コンストラクタ
	Consumer(Warehouse obj) {
		msg = obj;
	}
	
	// スレッドの run() メソッド
	public void run() {
		// 共有オブジェクトの値を5回ゲット
		for (int i = 0; i < 5; i++) {
			int val = msg.get();
			System.out.println("Consumer: " + val);
		}
	}
}

コントロールクラス

以上のクラスをインスタンス化して利用するためのテストクラスです。

ConsProdTest.java:

class ConsProdTest {
	public static void main(String[] args) {
		// 共有オブジェクト
		Warehouse share = new Warehouse();
		// 読み取りスレッド
		Consumer cons = new Consumer(share);
		// 書き込みスレッド
		Producer prod = new Producer(share);
		
		// ランナブルクラスをスレッドに受け渡し
		Thread threCons = new Thread(cons);
		Thread threProd = new Thread(prod);
		
		// スレッド開始
		threCons.start();
		threProd.start();
	}
}

実行例

C:\java\thread>javac ConsProdTest.java
C:\java\thread>java ConsProdTest
Consumer: 0
Producer: 0
Consumer: 1
Producer: 1
Consumer: 2
Producer: 2
Producer: 3
Consumer: 3
Consumer: 4
Producer: 4
C:\java\thread>


Copyright © 2002 SUGAI, Manabu. All Rights Reserved.