Java併発の従来スレッド同期通信技術コード詳細


本論文では主にJava併発の従来のスレッド同期通信技術に関するコード例を紹介します。
まず問題を見ます。
二つのスレッドがあります。最初に10回のスレッドを実行してから、メインスレッドを5回実行し、その後にサブスレッドに切り替えて10を実行し、メインスレッドを5回実行します。
この問題を見終わったら、スレッド間の通信を使うことが明らかになりました。まず二つのスレッドがあります。そして各スレッドの中には必ず50回のループがあります。スレッドごとに50回のタスクがあります。メインスレッドのタスクは5回、サブスレッドのタスクは10回実行します。スレッド間通信技術は主にwait()方法とnotify()方法を使用する。wait()方法は、現在のスレッドの待ち時間を引き起こし、保持されているロックを解放し、notify()方法は、このオブジェクトモニタ上で待つ単一のスレッドを起動することを示す。次はこのスレッド間通信問題を一歩ずつ完成させます。
まずメインスレッドとサブスレッドの間の通信を考慮しないで、各スレッドの実行するタスクを書き込みます。

public class TraditionalThreadCommunication {
	public static void main(String[] args) {
		//       
		new Thread(new Runnable() {
			@Override
			      public void run() {
				for (int i = 1; i <= 50; i ++) {
					synchronized (TraditionalThreadCommunication.class) {
						//     :  10         
						for (int j = 1;j <= 10; j ++) {
							System.out.println("sub thread sequence of " + j + ", loop of " + i);
						}
					}
				}
			}
		}
		).start();
		//main      
		for (int i = 1; i <= 50; i ++) {
			synchronized (TraditionalThreadCommunication.class) {
				//     :  5 
				for (int j = 1;j <= 5; j ++) {
					System.out.println("main thread sequence of " + j + ", loop of " + i);
				}
			}
		}
	}
}
以上のように、2つのスレッドはそれぞれ50回の大きなループを持ち、50回のタスクを実行し、サブスレッドのタスクは10回実行し、メインスレッドのタスクは5回実行する。二つのスレッド間の同期問題を保証するために、synchronized同期コードブロックを使用し、同じロック:クラスのバイトコードオブジェクトを使用した。これはスレッドの安全を保証することができます。しかし、このデザインはあまりよくないです。前のセクションのデッドロックに書いたように、スレッドタスクを一つのクラスに置くことができます。このようなデザインのパターンはもっと構造化されています。また、同じクラスにスレッドタスクを置くと同期問題が解決しやすくなります。上のプログラムを修正します。

public class TraditionalThreadCommunication {
	public static void main(String[] args) {
		Business bussiness = new Business();
		//new         
		//       
		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 {
	public synchronized void sub(int i) {
		for (int j = 1;j <= 10; j ++) {
			System.out.println("sub thread sequence of " + j + ", loop of " + i);
		}
	}
	public synchronized void main(int i) {
		for (int j = 1;j <= 5; j ++) {
			System.out.println("main thread sequence of " + j + ", loop of " + i);
		}
	}
このように修正した後、プログラムの構造がより明確になりました。もっと丈夫になりました。二つのスレッドのタスク方法にsynchronizedキーワードを加えればいいです。このロックはすべてthisです。しかし、現在の2つのスレッド間では通信が行われていません。実行の結果、メインスレッドが50回循環してタスクを50回実行します。その理由は簡単です。
以下は引き続きプログラムを充実させ、二つのスレッドの間でテーマに記述されているように通信させる。

public class TraditionalThreadCommunication {
	public static void main(String[] args) {
		Business bussiness = new Business();
		//new         
		//       
		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 {
	private Boolean bShouldSub = true;
	public synchronized void sub(int i) {
		while(!bShouldSub) {
			//         ,  
			try {
				this.wait();
				//  wait()        synchronized     ,  synchronized    ,   this
			}
			catch (InterruptedException 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;
		//    
		this.notify();
		//          
	}
	public synchronized void main(int i) {
		while(bShouldSub) {
			//         ,  
			try {
				this.wait();
			}
			catch (InterruptedException 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 = true;
		//    
		this.notify();
		//          
	}
}
まず、具体的なプログラムの実現はともかく、構造的に見て、メイン関数では何も修正しなくてもいいです。スレッド間同期とスレッド間通信に関するロジックはすべてBusiness類にあります。メイン関数の異なるスレッドは、このクラスで対応するタスクを呼び出すだけでいいです。高類聚のメリットを体現しています。
具体的なコードを見てください。まずブーメラン型変数を定義して、どのスレッドが実行されているかを識別します。サブスレッドが実行されていないときは、そのまま寝ると、自然にメインスレッドが実行され、実行が完了し、bShouldSubが修正されて、子スレッドが起動されます。この時、whileが満たされていないと判断します。メインスレッドがbShouldSubを修正したばかりで、第二のループがメインスレッドのタスクを実行するときは、whileが満足していると判断して寝てしまい、サブスレッドが起動するのを待ちます。このようにロジックははっきりしています。メインスレッドと子スレッドは次のように順番にそれぞれのタスクを実行します。このリズムは全部で50回循環します。
もう一つの小さな説明があります。ここではifで判断してもいいですが、なぜwhileを使うのですか?スレッドが目を覚ましてしまうことがあるので(人の夢のように寝ていますが、結果は立っています)、ifを使っていると、偽の覚醒後には、ifを判断するために戻ってこないです。自然に次のタスクを実行します。はい、他のスレッドが実行しています。しかし、whileの場合は違っています。スレッド休みが起きても、whileのことを判断しますが、この時はもう一つのスレッドが実行されています。bShordSubは修正されていません。だから、whileの中に入りました。また寝られました。だから、安全です。他のスレッドに影響はありません。公式JDK文書でもこのようにしています。
スレッド間通信はここまでにまとめましょう。
締め括りをつける
以上はJava併発の伝統的なスレッド同期通信技術コードについて詳しく説明した内容です。興味のある方は引き続き当駅の他のテーマを参照してください。友達のサポートに感謝します。