Object類wait,notify,notifyAll的使用

這三個方法是java的基礎類Object中定義的。

 

wait()

JDk文檔寫道
在其他線程調用此對象的 notify() 方法或 notifyAll() 方法前,導致當前線程等待。換句話說,此方法的行爲就好像它僅執行 wait(0) 調用一樣。
當前線程必須擁有此對象監視器 。該線程發佈對此監視器的所有權並等待,直到其他線程通過調用 notify 方法,或 notifyAll 方法通知在此對象的監視器上等待的線程醒來。然後該線程將等到重新獲得對監視器的所有權後才能繼續執行。

對於某一個參數的版本,實現中斷和虛假喚醒是可能的,而且此方法應始終在循環中使用:

synchronized (obj) {
while (<condition does not hold>)
obj.wait();
... // Perform action appropriate to condition
}
此方法只應由作爲此對象監視器的所有者的線程來調用。
拋出:
IllegalMonitorStateException - 如果當前線程不是此對象監視器的所有者。
InterruptedException - 如果在當前線程等待通知之前或者正在等待通知時,任何線程中斷了當前線程。在拋出此異常時,當前線程的中斷狀態 被清除。

 對於紅色部分的內容,個人曾一直都不是很理解,什麼叫做擁有此對象的監視器。下面我們看看代碼:

 

	DateFormat format = new SimpleDateFormat("yyyy-MM-dd:hh:mm:ss");
	
	private String getTime(){
		return format.format(Calendar.getInstance().getTime());
	}
	
	private Object monitor = new Object();


        public void waitOnce(String thread, final long ms) {
		Thread waitThread = new Thread() {
			public void run() {
				synchronized (monitor) {//獲得對象監視器

					try {
						System.out.println("Thread "
								+ Thread.currentThread().getName()
								+ " Wait at " + getTime());
						monitor.wait(ms);
						System.out.println("Thread "
								+ Thread.currentThread().getName()
								+ " Waked at " + getTime());
					} catch (InterruptedException e) {
					}
				}
			};
		};
		waitThread.setName(thread);
		waitThread.start();
	}

 

如果我們去掉synchronized(monitor) ,運行則會出現異常IllegalMonitorStateException。

 

		WaitAndNotifyTest test = new WaitAndNotifyTest();
		test.waitOnce("1", Long.MAX_VALUE);
寫道
Exception in thread "1" java.lang.IllegalMonitorStateException

 而加上以後就沒問題了。因此個人覺得使用synchronized關鍵字鎖定對象,也就是獲得了對象的監視器了。

 

notify()

JDK文檔 寫道
喚醒在此對象監視器上等待的單個線程。如果所有線程都在此對象上等待,則會選擇喚醒其中一個線程 。選擇是任意性 的,並在對實現做出決定時發生。線程通過調用其中一個 wait 方法,在對象的監視器上等待。

直到當前線程放棄此對象上的鎖定,才能繼續執行被喚醒的線程 。被喚醒的線程將以常規方式與在該對象上主動同步的其他所有線程進行競爭;例如,喚醒的線程在作爲鎖定此對象的下一個線程方面沒有可靠的特權或劣勢。

此方法只應由作爲此對象監視器的所有者的線程來調用。通過以下三種方法之一,線程可以成爲此對象監視器的所有者:

通過執行此對象的同步實例方法。
通過執行在此對象上進行同步的 synchronized 語句的正文。
對於 Class 類型的對象,可以通過執行該類的同步靜態方法。


一次只能有一個線程擁有對象的監視器。

拋出:
IllegalMonitorStateException - 如果當前線程不是此對象監視器的所有者。

 首先理解一下獲得對象的監視器,簡單的說就是取得了當前對象的“加鎖”使用權,最簡單的就是使用synchronized關鍵字。另外使用 synchronized修飾的方法也行。

 

notify方法還有一個值得提出的是它會在當前線程釋放了對象鎖以後隨機喚醒一個在該對象上等待的線程

看看一個例子:

	public void awakeAndWait(String thread, final long ms) {
		Thread notifyThread = new Thread() {
			public void run() {
				synchronized (monitor) {
					monitor.notify();
					System.out.println("Thread "
							+ Thread.currentThread().getName() + " Notify at "
							+ getTime());
					//保持了對象鎖的等待
					try {
						Thread.sleep(ms);
					} catch (InterruptedException e) {
					}
				}
				//釋放了對象鎖之後的等待
				try {
					Thread.sleep(ms);
				} catch (InterruptedException e) {
				}
			};
		};
		notifyThread.setName(thread);
		notifyThread.start();
	}

 這個方法會喚醒一個在對象上等待的線程,並在兩次sleep後退出,注意的是一個sleep是在對象鎖內,而另一次則是在釋放了對象鎖以後,這時候運行上面2個方法得到:

		WaitAndNotifyTest test = new WaitAndNotifyTest();
		test.waitOnce("1", Long.MAX_VALUE);// 在對象上等待無限長
		test.waitOnce("2", Long.MAX_VALUE);// 在對象上等待無限長
		test.waitOnce("3", Long.MAX_VALUE);// 在對象上等待無限長
		try {// 延遲2s
			Thread.sleep(2000);
		} catch (InterruptedException e) {
		}
		// 在喚醒一個在對象上等待的線程,本身執行時間4s,2s是在對象鎖內
		//,2s是在釋放了對象鎖以後
		test.awakeAndWait("3", 2000);

 執行結果爲:

寫道
Thread 1 Wait at 2011-05-06:10:57:04
Thread 2 Wait at 2011-05-06:10:57:04
Thread 3 Wait at 2011-05-06:10:57:04
Thread 3 Notify at 2011-05-06:10:57:06
Thread 1 Waked at 2011-05-06:10:57:08

 2秒後喚醒了線程1,儘管它自己執行花了4s,在釋放了對象鎖之後的2s不會影響線程1的執行。

 

notifyAll()

JDK文檔 寫道
喚醒在此對象監視器上等待的所有線程 。線程通過調用其中一個 wait 方法,在對象的監視器上等待。
直到當前線程放棄此對象上的鎖定,才能繼續執行被喚醒的線程。被喚醒的線程將以常規方式與在該對象上主動同步的其他所有線程進行競爭;例如,喚醒的線程在作爲鎖定此對象的下一個線程方面沒有可靠的特權或劣勢。

此方法只應由作爲此對象監視器的所有者的線程來調用。有關線程能夠成爲監視器所有者的方法的描述,請參閱 notify 方法。

拋出:
IllegalMonitorStateException - 如果當前線程不是此對象監視器的所有者。

 與notify稍微有一點差別的是,它會喚醒所有的等待線程。

 

	public void awakeAll(String thread) {
		Thread notifyThread = new Thread() {
			public void run() {
				synchronized (monitor) {
					monitor.notifyAll();
					System.out.println("Thread "
							+ Thread.currentThread().getName()
							+ " Notify all at " + getTime());
				}
			};
		};
		notifyThread.setName(thread);
		notifyThread.start();
	}

 執行一下代碼:

		WaitAndNotifyTest test = new WaitAndNotifyTest();
		test.waitOnce("1", Long.MAX_VALUE);// 在對象上等待無限長
		test.waitOnce("2", Long.MAX_VALUE);// 在對象上等待無限長
		test.waitOnce("3", Long.MAX_VALUE);// 在對象上等待無限長
		try {// 延遲2s
			Thread.sleep(2000);
		} catch (InterruptedException e) {
		}
		test.awakeAll("4");

 結果爲:

寫道
Thread 1 Wait at 2011-05-06:10:59:15
Thread 3 Wait at 2011-05-06:10:59:15
Thread 2 Wait at 2011-05-06:10:59:15
Thread 4 Notify all at 2011-05-06:10:59:17
Thread 2 Waked at 2011-05-06:10:59:17
Thread 3 Waked at 2011-05-06:10:59:17
Thread 1 Waked at 2011-05-06:10:59:17

 全部喚醒了。

 

總結

總結一下:大概有以下幾點:

  1. wait(),notify(),notifyAll()都需要在擁有對象監視器的前提下執行,否則會出現異常IllegalMonitorStateException。
  2. 多個線程可以同時在一個對象上等待。
  3. notify()將隨機喚醒一個在對象上等待的線程,沒有一個都沒有,則什麼都不做。
  4. notify()喚醒的線程,將在notify()線程釋放了對象監視器以後才執行,並不是notify了以後馬上執行。
  5. Object的這些方法與Thread的sleep、interrupt相差還是很遠的,不要混爲一談了。
發佈了29 篇原創文章 · 獲贊 4 · 訪問量 4816
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章