Java併発の条件閉塞Conditionの応用コード例


本論文で研究したのは主にJava併発の条件閉塞Conditionの応用例コードであり、具体的には以下の通りである。
Condationは、Objectモニタ方法(wait、notify、notifyAll)とは異なるオブジェクトに分解し、これらのオブジェクトを任意のLockと組み合わせて使用することにより、各オブジェクトに複数の待受setを提供する。ここで、ロックはsynchronized方法と文の使用に代わり、Objectモニタ方法の使用をCondationが代替しています。
1.コンディショニングの基本使用
Condationはwait、notifyなどの代わりに使えるので、前に書いたスレッド間通信のコードを比べてみてもいいです。
二つのスレッドがあります。最初に10回のスレッドを実行してから、メインスレッドを5回実行し、その後にサブスレッドに切り替えて10を実行し、メインスレッドを5回実行します。
以前はwaitとnotifyで実現しましたが、今はCoditionで書き換えてみます。コードは以下の通りです。

public class ConditionCommunication {
	public static void main(String[] args) {
		Business bussiness = new Business();
		new Thread(new Runnable() {
			//        
			@Override
			          public void run() {
				for (int i = 1; i <= 50; i++) {
					bussiness.sub(i);
				}
			}
		}
		).start();
		// main     
		for (int i = 1; i <= 50; i++) {
			bussiness.main(i);
		}
	}
}
class Business {
	Lock lock = new ReentrantLock();
	Condition condition = lock.newCondition();
	//Condition     lock   
	private Boolean bShouldSub = true;
	public void sub(int i) {
		lock.lock();
		try {
			while (!bShouldSub) {
				try {
					condition.await();
					// condition   await  
				}
				catch (Exception e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
			for (int j = 1; j <= 10; j++) {
				System.out.println("sub thread sequence of " + j
				            + ", loop of " + i);
			}
			bShouldSub = false;
			condition.signal();
			// condition       ,     
		}
		finally {
			lock.unlock();
		}
	}
	public void main(int i) {
		lock.lock();
		try {
			while (bShouldSub) {
				try {
					condition.await();
					// condition   await  
				}
				catch (Exception e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
			for (int j = 1; j <= 10; j++) {
				System.out.println("main thread sequence of " + j
				            + ", loop of " + i);
			}
			bShouldSub = true;
			condition.signal();
			// condition        ,     
		}
		finally {
			lock.unlock();
		}
	}
}
コードから見ると、Condationはロックと一緒に使われていますが、ロックがないとCondationは使えません。Condationはロックでnewが出てきますので、このような使い方は簡単です。synchronizedとwait、notifyの使用をマスターすれば、ロックとCondationの使用を完全に把握できます。
2.コンディショニングの高さ
2.1バッファエリアのブロック列
上にはロックとCondationを使って、synchronizedとObjectのモニター方法の代わりに2つのスレッド間の通信を実現しました。今はもうちょっと高級なアプリケーションを書きます。アナログバッファエリアのブロック列です。
緩衝区とは何ですか?例えば、今はたくさんの人がメッセージを送ります。私は中継所です。他の人にメッセージを送るように手伝いたいです。それでは今は2つのことをしなければなりません。一つのことはユーザーからのメッセージを受け取って、バッファエリアに順番に置いて、もう一つのことはバッファエリアから順番にユーザからのメッセージを取り出して、送信します。
この現実的な問題を抽象してみます。バッファエリアはつまり行列です。データをアレイに書き込むことができます。配列からデータを取ることもできます。私がやるべきことは二つのスレッドを開いて、一つはデータを蓄えて、一つはデータを取ります。しかし、問題が来ました。もしバッファが満杯になったら、受信したメッセージが多すぎると説明しました。つまり、送ってきたメッセージが速すぎて、もう一つのスレッドがまだ終わっていないので、バッファエリアはどこにも置かれていません。この時はデータを保存するスレッドをブロックして、待ちます。逆に、転送が早すぎると、バッファエリアのすべてのコンテンツが私によって送信されました。まだユーザーから新しいメッセージが来ていないので、この時点でデータを取るスレッドがブロックされます。
はい、この緩衝区の渋滞行列を分析しました。次はCondation技術で実現します。

class Buffer {
	final Lock lock = new ReentrantLock();
	//     
	final Condition notFull = lock.newCondition();
	//         Condition
	final Condition notEmpty = lock.newCondition();
	//         Condition
	final Object[] items = new Object[10];
	//      ,          10,     
	int putptr, takeptr, count;
	//    ,       
	//       
	public void put(Object x) throws InterruptedException {
		lock.lock();
		//  
		try {
			while (count == items.length) {
				System.out.println(Thread.currentThread().getName() + "     ,       !");
				notFull.await();
				//      ,           ,     
			}
			//    ,        
			items[putptr] = x;
			if (++putptr == items.length) //           ,    ,     
			putptr = 0;
			++count;
			//    
			System.out.println(Thread.currentThread().getName() + "     : " + x);
			notEmpty.signal();
			//  ,         ,          ,      
		}
		finally {
			lock.unlock();
			//  
		}
	}
	//       
	public Object take() throws InterruptedException {
		lock.lock();
		//  
		try {
			while (count == 0) {
				System.out.println(Thread.currentThread().getName() + "     ,       !");
				notEmpty.await();
				//      ,           ,     
			}
			//    ,        
			Object x = items[takeptr];
			if (++takeptr == items.length) //        ,    ,     
			takeptr = 0;
			--count;
			//    
			System.out.println(Thread.currentThread().getName() + "     : " + x);
			notFull.signal();
			//  ,         ,          ,      
			return x;
		}
		finally {
			lock.unlock();
			//  
		}
	}
}
このプログラムはとてもクラシックです。公式のJDK文書から取り出して、注釈をつけました。プログラムには二つのCondationが定義されています。それぞれ二つのスレッドに対して、待ち時間と目覚ましはそれぞれ異なるCondationで実行します。考えがはっきりしていて、プログラムも丈夫です。一つの問題を考えてもいいです。なぜ二つのCoditionを使いますか?なぜこのように設計されたのかには理由があります。もし一つのCondationを使うと、今は列がいっぱいになったとしても、二つのスレッドAとBが同時にデータを保存しています。それでは全部睡眠に入りました。この時は列がいっぱいで、Bは貯められません。Bは元の値に上書きされます。一つのコンディショニングを使っていますので、貯蓄と取りはこのコンディショニングで睡眠と目覚ましが乱れています。ここまで来れば、このコンスタントの使い道が分かります。上の列の渋滞効果をテストしてみます。

public class BoundedBuffer {
	public static void main(String[] args) {
		Buffer buffer = new Buffer();
		for (int i = 0; i < 5; i ++) {
			//  5          
			new Thread(new Runnable() {
				@Override
				        public void run() {
					try {
						buffer.put(new Random().nextint(1000));
						//     
					}
					catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
			}
			).start();
		}
		for (int i = 0; i < 10; i ++) {
			//  10           
			new Thread(new Runnable() {
				@Override
				        public void run() {
					try {
						buffer.take();
						//       
					}
					catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
			}
			).start();
		}
	}
}
わざと5スレッドだけを開いてデータを貯めています。10スレッドでデータを取ると、データを取ってブロックされることが発生します。運行の結果を見ます。
Thread-5がブロックされました。しばらくはデータを取ることができません。
Thread-10がブロックされました。しばらくはデータを取ることができません。
Thread-1の保存済み値:755
Thread-0の保存済み値:206
Thread-2の保存済み値:741
Thread-3はよく保存しました。値:381
Thread-14取出値:755
Thread-4が保存されました。値:783
Thread-6取出値:206
Thread-7は値を取り出しました。741
Thread-8取出値:381
Thread-9取出値:783
Thread-5がブロックされました。しばらくはデータを取ることができません。
Thread-11がブロックされました。しばらくはデータを取ることができません。
Thread-12がブロックされました。しばらくはデータを取ることができません。
Thread-10がブロックされました。しばらくはデータを取ることができません。
Thread-13はブロックされました。しばらくはデータを取ることができません。
その結果、スレッド5と10は先に実行していますが、列の中にはなかったので、ブロックされてしまいました。列の中に新しい値が入るまで取ってもいいです。セーブデータがブロックされているのを見たいなら、データを取るスレッドを少なく設定してもいいです。ここではセットしません。
2.2二つ以上のスレッド間の起動
元のテーマです。今は三つのスレッドを実行させて、テーマを見てみます。
3つのスレッドがあり、サブスレッド1はまず10回、そしてサブスレッド2は10回、そしてメインスレッド5回、そしてサブスレッド1は10回、サブスレッド2は10回、メインスレッドは5回、と50回繰り返します。
Condationを使わないと、本当にやりにくいですが、Condationを使うと、とても便利です。原理は簡単です。三つのCondationを定義し、子スレッド1を実行して、子スレッド2を起動して、メインスレッド1を実行します。目覚ましメカニズムは上のバッファと同じです。コードを見てみましょう。分かりやすいです。

public class ThreeConditionCommunication {
	public static void main(String[] args) {
		Business bussiness = new Business();
		new Thread(new Runnable() {
			//        
			@Override
			          public void run() {
				for (int i = 1; i <= 50; i++) {
					bussiness.sub1(i);
				}
			}
		}
		).start();
		new Thread(new Runnable() {
			//         
			@Override
			      public void run() {
				for (int i = 1; i <= 50; i++) {
					bussiness.sub2(i);
				}
			}
		}
		).start();
		// main     
		for (int i = 1; i <= 50; i++) {
			bussiness.main(i);
		}
	}
	static class Business {
		Lock lock = new ReentrantLock();
		Condition condition1 = lock.newCondition();
		//Condition     lock   
		Condition condition2 = lock.newCondition();
		Condition conditionMain = lock.newCondition();
		private int bShouldSub = 0;
		public void sub1(int i) {
			lock.lock();
			try {
				while (bShouldSub != 0) {
					try {
						condition1.await();
						// condition   await  
					}
					catch (Exception e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
				}
				for (int j = 1; j <= 10; j++) {
					System.out.println("sub1 thread sequence of " + j
					              + ", loop of " + i);
				}
				bShouldSub = 1;
				condition2.signal();
				//   2  
			}
			finally {
				lock.unlock();
			}
		}
		public void sub2(int i) {
			lock.lock();
			try {
				while (bShouldSub != 1) {
					try {
						condition2.await();
						// condition   await  
					}
					catch (Exception e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
				}
				for (int j = 1; j <= 10; j++) {
					System.out.println("sub2 thread sequence of " + j
					              + ", loop of " + i);
				}
				bShouldSub = 2;
				conditionMain.signal();
				//      
			}
			finally {
				lock.unlock();
			}
		}
		public void main(int i) {
			lock.lock();
			try {
				while (bShouldSub != 2) {
					try {
						conditionMain.await();
						// condition   await  
					}
					catch (Exception e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
				}
				for (int j = 1; j <= 5; j++) {
					System.out.println("main thread sequence of " + j
					              + ", loop of " + i);
				}
				bShouldSub = 0;
				condition1.signal();
				//   1  
			}
			finally {
				lock.unlock();
			}
		}
	}
}
コードはちょっと長いようですが、仮説です。ロジックはとても簡単です。スレッドの中のCondation技術についてはこれだけまとめましょう。
締め括りをつける
以上がJava併発の条件閉塞Condationのアプリケーションコード例の全部です。興味のある方は引き続き当駅の他のテーマを参照してください。友達のサポートに感謝します。