JAvaのThread_wait、notify、notifyAllの使い方

10806 ワード

wait()、notify()、notifyAll()は、Objectクラスに定義された3つのメソッドで、スレッドの状態を制御するために使用できます.
この3つのメソッドは最終的にjvmレベルのnativeメソッドを呼び出す.jvmの実行プラットフォームによって多少の違いがある場合があります.•オブジェクトがwaitメソッドを呼び出すと、オブジェクトを持つスレッドがオブジェクトの制御権を渡し、待機状態になります.•オブジェクトがnotifyメソッドを呼び出すと、オブジェクトの制御権を待機しているスレッドが実行を続行できることが通知されます.•オブジェクトがnotifyAllメソッドを呼び出すと、このオブジェクトの制御権を待つすべてのスレッドが実行を続行することを通知します.
ここでwaitメソッドには3つのover loadメソッドがある:wait()wait(long)wait(long,int)
waitメソッドは、パラメータによって待機時間を指定できます.パラメータが指定されていない場合は、通知されるまでデフォルトで待機します.
以下は、複雑な問題を最も簡潔に説明するプレゼンテーションコードです.簡単に説明します.
1.NotifyThreadは、3秒後に他の待機状態を通知するスレッドをシミュレートするためのスレッドクラスである.
2.WaitThreadは待ち時間をシミュレートするためのスレッドクラスである.
3.待機中の中間オブジェクトはflagであり、Stringオブジェクトである.
mainメソッドでは、1つのNotifyスレッドと3つのwaitスレッドを同時に起動します.
package thread;
public class NotifyTest {
	private  String flag = "true";
	class NotifyThread extends Thread{   
		public NotifyThread(String name) {   
			super(name);
		}
		
		public void run() {        
			try {
				sleep(3000);//  3       
			} catch (InterruptedException e) {   
				e.printStackTrace();   
			}   
			System.out.println("1111111111111");
			flag = "false";   
			 flag.notify();   
		}   
	};   

	class WaitThread extends Thread {   
		public WaitThread(String name) {   
			super(name);   
		}   

		public void run() {   
			while (flag!="false") {   
				System.out.println(getName() + " begin waiting!");   
				long waitTime = System.currentTimeMillis();   
				
				try {   
					flag.wait();   
				} catch (InterruptedException e) {   
					e.printStackTrace();   
				}   
				
				waitTime = System.currentTimeMillis() - waitTime;   
				System.out.println("wait time :"+waitTime);   
			}   
			System.out.println(getName() + " end waiting!");   
		}   
	}   

	public static void main(String[] args) throws InterruptedException {   
		System.out.println("Main Thread Run!");   
		NotifyTest test = new NotifyTest();   
		NotifyThread notifyThread =test.new NotifyThread("notify01");   
		WaitThread waitThread01 = test.new WaitThread("waiter01");   
		WaitThread waitThread02 = test.new WaitThread("waiter02");   
		WaitThread waitThread03 = test.new WaitThread("waiter03");   
		notifyThread.start();   
		waitThread01.start();   
		waitThread02.start();   
		waitThread03.start();   
	}   
 }

OK、このプログラムを実行すると、全然実行できないことに気づきます.whappened?フルスクリーンjava.lang.IllegalMonitorStateException.
そうですね.このプログラムには多くの問題があります.私たちは一人一人見てみましょう.まず、ここで注意しなければならないいくつかの事実は、
1.いつでも、オブジェクトの制御権(monitor)は、1つのスレッドのみが持つことができます.2.実行オブジェクトのwait、notify、notifyAllメソッドにかかわらず、現在実行中のスレッドがそのオブジェクトの制御権を取得することを保証する必要があります.(monitor)3.制御権のないスレッドでオブジェクトを実行する以上の3つの方法ではjava.lang.I llegalMonitorStateException異常が報告されます.4.JVMはマルチスレッドベースで、デフォルトではランタイムスレッドのタイミング性は保証されません
以上の事実に基づいて,スレッドにオブジェクトの制御権を持たせる必要がある.すなわちwaitThreadでwaitメソッドを実行する場合、waitThreadがflagに対して制御権を持つことを保証する.notifyThreadでnotifyメソッドを実行する場合は、notifyThreadがflagに対して制御権を持つことを保証します.
 
スレッドが制御権を取得する方法は3つある:1.オブジェクトの同期インスタンスメソッドを実行します.2.オブジェクト対応クラスの同期静的メソッドを実行します.3.オブジェクトに同期ロックをかける同期ブロックを実行します.
3つ目の方法で説明します.上記のnotifyメソッドとwaitメソッドを同期ブロックにパッケージします.
package thread;
public class NotifyTest {
	private  String flag = "true";
	class NotifyThread extends Thread{   
		public NotifyThread(String name) {   
			super(name);
		}
		
		public void run() {        
			try {
				sleep(3000);//  3       
			} catch (InterruptedException e) {   
				e.printStackTrace();   
			} 
			synchronized (flag) {
			System.out.println("1111111111111");
			flag = "false";   
			 flag.notify(); 
			}
		}   
	};   

	class WaitThread extends Thread {   
		public WaitThread(String name) {   
			super(name);   
		}   

		public void run() {
			synchronized (flag) {  
			while (flag!="false") {   
				System.out.println(getName() + " begin waiting!");   
				long waitTime = System.currentTimeMillis();   
				
				try {   
					flag.wait();   
				} catch (InterruptedException e) {   
					e.printStackTrace();   
				}   
				
				waitTime = System.currentTimeMillis() - waitTime;   
				System.out.println("wait time :"+waitTime);   
			}   
			System.out.println(getName() + " end waiting!");   
		}   
		}
	}   

	public static void main(String[] args) throws InterruptedException {   
		System.out.println("Main Thread Run!");   
		NotifyTest test = new NotifyTest();   
		NotifyThread notifyThread =test.new NotifyThread("notify01");   
		WaitThread waitThread01 = test.new WaitThread("waiter01");   
		WaitThread waitThread02 = test.new WaitThread("waiter02");   
		WaitThread waitThread03 = test.new WaitThread("waiter03");   
		notifyThread.start();   
		waitThread01.start();   
		waitThread02.start();   
		waitThread03.start();   
	}   
 }

私たちは一歩前進した.問題は解決しましたか.実行中かjavaを間違えたようです.lang.IllegalMonitorStateException.what happened?
このときの異常は、flagオブジェクト同期ブロックにおいて、flagオブジェクトの状態が変更されたためである.flag=「false」、flag.notify();
同期ブロックでflagを付与操作してflag参照のオブジェクトを変更し、notifyメソッドを呼び出すと制御権がないため異常が放出される.flagをJavaBeanに変更し、そのプロパティを変更してもflagの参照には影響しません.ここで配列に変更してみても、同じ効果が得られます.
package thread;
public class NotifyTest {
	private   String flag[] = {"true"};   
 	class NotifyThread extends Thread{   
		public NotifyThread(String name) {   
			super(name);
		}
		
		public void run() {        
			try {
				sleep(3000);//  3       
			} catch (InterruptedException e) {   
				e.printStackTrace();   
			} 
			synchronized (flag) {
			System.out.println("1111111111111");
			 flag[0] = "false";  
			 flag.notify(); 
			}
		}   
	};   

	class WaitThread extends Thread {   
		public WaitThread(String name) {   
			super(name);   
		}   

		public void run() {
			synchronized (flag) {  
			 while (flag[0]!="false") {    
				System.out.println(getName() + " begin waiting!");   
				long waitTime = System.currentTimeMillis();   
				
				try {   
					flag.wait();   
				} catch (InterruptedException e) {   
					e.printStackTrace();   
				}   
				
				waitTime = System.currentTimeMillis() - waitTime;   
				System.out.println("wait time :"+waitTime);   
			}   
			System.out.println(getName() + " end waiting!");   
		}   
		}
	}   

	public static void main(String[] args) throws InterruptedException {   
		System.out.println("Main Thread Run!");   
		NotifyTest test = new NotifyTest();   
		NotifyThread notifyThread =test.new NotifyThread("notify01");   
		WaitThread waitThread01 = test.new WaitThread("waiter01");   
		WaitThread waitThread02 = test.new WaitThread("waiter02");   
		WaitThread waitThread03 = test.new WaitThread("waiter03");   
		notifyThread.start();   
		waitThread01.start();   
		waitThread02.start();   
		waitThread03.start();   
	}   
 }

この時点で運転し、異常は報告されませんが、スレッドは終了していませんよね、間違いなく、スレッドが詰まってwait状態にあります.理由は簡単です.私たちは3つのwaitスレッドを持っています.1つのnotifyスレッドしかありません.notifyスレッドがnotifyメソッドを実行するときは、待機しているスレッドをランダムに通知します.そのため、waitingには2つのスレッドがあるはずです.NotifyThreadスレッドクラスのflagをnotify()メソッドをnotifyAll()に変更すればよい.notifyAllメソッドは、オブジェクト制御権を待機しているすべてのスレッドに通知します.
synchronized (flag) {  
                flag[0] = "false";  
                flag.notifyAll();  
            } 

他のブログに抜粋した記事を追加します.
マルチスレッドの場合、同じプロセスの複数のスレッドが同じストレージ空間を共有するため、便利さをもたらすとともに、アクセス競合という深刻な問題ももたらす.Java言語は、この競合を解決するための専門的なメカニズムを提供し、同じデータ・オブジェクトが複数のスレッドで同時にアクセスされることを効果的に回避します.
waitとnotifyはjava同期メカニズムの重要な構成部分である.synchronizedキーワードと組み合わせて使用すると、多くの優れた同期モデルを構築できます.synchronized(this){}publicsynchronized void method(){.....}に等しい同期はクラス・レベルとオブジェクト・レベルに分けられ、それぞれクラス・ロックとオブジェクト・ロックに対応します.クラスロックは各クラスに1つしかありません.staticのメソッドがsynchronizedキーワードで修飾されている場合、このメソッドが実行される前にクラスロックを取得する必要があります.オブジェクト・ロックは同じです.まず、1つのObjectのwaitとnotify/notifyAllを呼び出すときは、呼び出しコードがそのObjectに同期していることを保証する必要があります.つまり、synchronized(obj){......}の内部でobjのwaitとnotify/notifyAllの3つのメソッドを呼び出すことができます.そうしないと、javaとエラーが発生します.lang.I llegalMonitorStateException:current thread not owner waitを呼び出すと、スレッドは占有するオブジェクトロックを自動的に解放し、オブジェクトロックを申請しません.スレッドが起動されると、オブジェクトロックを取得する権利が再び取得されます.したがって、notifyはnotifyAllとあまり変わらないが、notifyは1つのスレッドのみを呼び出し、ロックを取得することを許可し、notifyAllはこのオブジェクトを待つすべてのスレッドを呼び出し、synchroniedブロック内のコードであれば、オブジェクトロックがなければ一歩も進まないオブジェクトロックを取得することを許可する.実際には、スレッドを起動することは、オブジェクトロックを取得して下に実行することを再許可することです.
notifyAllは、waitのオブジェクトごとにnotifyを1回呼び出すが、これは順序があり、各オブジェクトはこの待機オブジェクトチェーンを保存し、呼び出す順序はこのチェーンの順序である.実際には,待機対象チェーン内の各スレッドを起動するのも1つのスレッドであり,具体的に適用する際には注意が必要である.
 
wait(),notify(),notifyAll()はThreadクラスではなくObjectベースクラス,すなわち各ペアにwait(),notify(),notifyAll()の機能がある.いずれも対像にロックがあるため、ロックは各対像の基礎であり、もちろんロックを操作する方法も最も基礎的である.
wait():
このメソッドを呼び出すには、オブジェクトの同期ロックを待つ必要があります.そうしないと、コンパイルは通過できますが、実行時に例外が発生します.IllegalMonitorStateExceptionです.
任意のオブジェクトを呼び出すwait()メソッドは、スレッドがブロックされ、スレッドが実行されず、オブジェクト上のロックが解放される.
notify():
オブジェクトの同期ロックを待機しているスレッド(1つだけ起動し、複数が待機している場合)を起動します.このメソッドを呼び出すと、待機状態のスレッドを正確に起動するのではなく、JVMによって起動するスレッドを決定し、優先度ではありません.
任意のオブジェクトのnotify()メソッドを呼び出すと、そのオブジェクトのwait()メソッドを呼び出すことによってブロックされたスレッドでランダムに選択されたブロック解除が発生します(ロックが取得されるまで実行できません).
notifyAll():
待機しているすべてのスレッドを起動します.起動したスレッドはnotifyより前のwaitであり、notifyより後のwaitスレッドには効果がありません.
 
通常、マルチスレッド間で調整作業が必要です.条件が満たされない場合は待機します.条件が満たされると、条件を待つスレッドが呼び出されます.Javaでは,このメカニズムの実現はwait/notifyに依存する.待機メカニズムはロックメカニズムと密接に関連している.
例えば、synchronized(obj){while(!condition){obj.wait();}obj.doSomething(); }スレッドAがobjロックを取得すると、条件conditionが満たされず、次の処理を続行できないことが判明し、スレッドAはwait()となる.別のスレッドBでは、スレッドAのcondition条件が満たされるように、Bがいくつかの条件を変更した場合、スレッドA:synchronized(obj){condition=true;obj.notify();}を呼び出す前にobjロックを取得する必要があることに注意してください.つまりsynchronized(obj){...}に書かなければなりませんコード・セグメント内.
#objを呼び出す.wait()後、スレッドAはobjのロックを解放します.そうしないと、スレッドBはobjロックを取得できません.synchronized(obj){...}コードセグメント内でAを起動します.♪obj.wait()メソッドが返されると、スレッドAは実行を続行するためにobjロックを再び取得する必要がある.A 1,A 2,A 3がobjである場合.wait()の場合、Bはobjを呼び出す.notify()は、A 1,A 2,A 3のいずれかしか起動できない(具体的にはどちらがJVMによって決定されるか).obj.notifyAll()は、A 1,A 2,A 3をすべて起動できるが、objを継続して実行する.wait()の次の文は、objロックを取得しなければならないので、A 1,A 2,A 3は、A 1のようなロックを取得して実行を継続する機会が1つしかありません.残りは、A 1がobjロックを解放してから実行を継続する必要があります.Bがobjを呼び出すと.notify/notifyAllの場合、Bはobjロックを持っているため、A 1,A 2,A 3は起動されてもobjロックは取得できない.Bがsynchronizedブロックを終了し、objロックが解放されるまで、A 1,A 2,A 3のいずれかがロックを取得して実行を継続する機会がある.  
synchronizedとwait()、notify()などの関係について話します.
1.synchronizedがあるところにwaitがあるとは限らない、notify
2.waitがあって、notifyの地方は必ずsynchronizedがあります.これはwaitとnotifyがスレッドクラスに属するのではなく、各オブジェクトが持つメソッドであり、この2つのメソッドはオブジェクトロックに関係しており、ロックがある場所にはsynchronizedが必要であるからです.
また、notifyとwaitメソッドを一緒に使用するには、waitが呼び出されるとcurrentthreadではないので、notifyを呼び出してからwaitを呼び出さなければなりません.