這三個方法是java的基礎(chǔ)類Object中定義的。
Java所有的類都具有線程的潛力,Java賦予的每個對象一個鎖,在計算機內(nèi)部工作在同一時間,只有一個對象可以持有鎖,也就是說程序在同一時間只有一個程序可以運行,這里我把對象比作是一個小的程序。而多處理器,那么就另當別論了。
在這里我們首先學(xué)習(xí)一下公共方法wait,notify,notifyAll。
wait方法可以使在當前線程的對象等待,直到別的線程調(diào)用此對象的notify或notifyAll方法(注意:調(diào)用的是此對象的notify和notifyAll),并且當前運行的線程必須具有此對象的對象監(jiān)視器,對象監(jiān)視器我們可以從三個方法中獲得,如下:
1.在執(zhí)行對象實例同步方法體中,可以獲得此對象的對象監(jiān)視器,例子偽代碼如下:
synchronized void aMethod(){
while(condition)
this.wait();
//other mothed; }
2.通過執(zhí)行對象同步synchronized正文,例子偽代碼如下:
synchronized(this){
while(condition)
this.wait();
//other mothed; }
3.對于 Class 類型的對象,可以通過執(zhí)行該類的同步靜態(tài)方法
這
個方法可以使當前對象滿足條件condition后,執(zhí)行等待,當前線程對象放棄鎖,cpu記錄當前線程狀態(tài),以備下次回復(fù)。然后讓其他線程運行,直到其
他線程調(diào)用此對象的notify或notifyAll方法,此對象才會重新獲得此對象的對象監(jiān)視器,此對象才能重新運行。
注意:調(diào)用這個方法,必須具有對象監(jiān)視器,也就是說我們必須在這三種方法選一種來獲得對象監(jiān)視器,如果調(diào)用此方法wait,卻沒用對象監(jiān)視器,那么運行時會拋出IllegalMonitorStateException.
而且,在靜態(tài)方法中也無法獲得對象監(jiān)視器,只能在Class類型的對象中,我們才可以通過調(diào)用該類的同步靜態(tài)方法來獲得對象監(jiān)視器。
wait() JDk文檔寫道
在其他線程調(diào)用此對象的 notify() 方法或 notifyAll() 方法前,導(dǎo)致當前線程等待。換句話說,此方法的行為就好像它僅執(zhí)行 wait(0) 調(diào)用一樣。 當前線程必須擁有此對象監(jiān)視器 。該線程發(fā)布對此監(jiān)視器的所有權(quán)并等待,直到其他線程通過調(diào)用 notify 方法,或 notifyAll 方法通知在此對象的監(jiān)視器上等待的線程醒來。然后該線程將等到重新獲得對監(jiān)視器的所有權(quán)后才能繼續(xù)執(zhí)行。
對于某一個參數(shù)的版本,實現(xiàn)中斷和虛假喚醒是可能的,而且此方法應(yīng)始終在循環(huán)中使用:
synchronized (obj) { while (<condition does not hold>) obj.wait(); ... // Perform action appropriate to condition }
此方法只應(yīng)由作為此對象監(jiān)視器的所有者的線程來調(diào)用。
拋出: IllegalMonitorStateException - 如果當前線程不是此對象監(jiān)視器的所有者。 InterruptedException - 如果在當前線程等待通知之前或者正在等待通知時,任何線程中斷了當前線程。在拋出此異常時,當前線程的中斷狀態(tài) 被清除。 對于紅色部分的內(nèi)容,個人曾一直都不是很理解,什么叫做擁有此對象的監(jiān)視器。下面我們看看代碼:
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) {//獲得對象監(jiān)視器 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(); }
如果我們?nèi)サ魋ynchronized(monitor) ,運行則會出現(xiàn)異常IllegalMonitorStateException。
WaitAndNotifyTest test = new WaitAndNotifyTest(); test.waitOnce("1", Long.MAX_VALUE);
寫道
Exception in thread “1″ java.lang.IllegalMonitorStateException
而加上以后就沒問題了。因此個人覺得使用synchronized關(guān)鍵字鎖定對象,也就是獲得了對象的監(jiān)視器了。
notify() JDK文檔 寫道
喚醒在此對象監(jiān)視器上等待的單個線程。如果所有線程都在此對象上等待,則會選擇喚醒其中一個線程 。選擇是任意性 的,并在對實現(xiàn)做出決定時發(fā)生。線程通過調(diào)用其中一個 wait 方法,在對象的監(jiān)視器上等待。
直到當前線程放棄此對象上的鎖定,才能繼續(xù)執(zhí)行被喚醒的線程 。被喚醒的線程將以常規(guī)方式與在該對象上主動同步的其他所有線程進行競爭;例如,喚醒的線程在作為鎖定此對象的下一個線程方面沒有可靠的特權(quán)或劣勢。
此方法只應(yīng)由作為此對象監(jiān)視器的所有者的線程來調(diào)用。通過以下三種方法之一,線程可以成為此對象監(jiān)視器的所有者:
通過執(zhí)行此對象的同步實例方法。 通過執(zhí)行在此對象上進行同步的 synchronized 語句的正文。 對于 Class 類型的對象,可以通過執(zhí)行該類的同步靜態(tài)方法。
一次只能有一個線程擁有對象的監(jiān)視器。
拋出: IllegalMonitorStateException – 如果當前線程不是此對象監(jiān)視器的所有者。
首先理解一下獲得對象的監(jiān)視器,簡單的說就是取得了當前對象的“加鎖”使用權(quán),最簡單的就是使用synchronized關(guān)鍵字。另外使用 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是在對象鎖內(nèi),而另一次則是在釋放了對象鎖以后,這時候運行上面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) { } // 在喚醒一個在對象上等待的線程,本身執(zhí)行時間4s,2s是在對象鎖內(nèi) //,2s是在釋放了對象鎖以后 test.awakeAndWait("3", 2000);
執(zhí)行結(jié)果為:
寫道
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,盡管它自己執(zhí)行花了4s,在釋放了對象鎖之后的2s不會影響線程1的執(zhí)行。
notifyAll() JDK文檔 寫道
喚醒在此對象監(jiān)視器上等待的所有線程 。線程通過調(diào)用其中一個 wait 方法,在對象的監(jiān)視器上等待。 直到當前線程放棄此對象上的鎖定,才能繼續(xù)執(zhí)行被喚醒的線程。被喚醒的線程將以常規(guī)方式與在該對象上主動同步的其他所有線程進行競爭;例如,喚醒的線程在作為鎖定此對象的下一個線程方面沒有可靠的特權(quán)或劣勢。
此方法只應(yīng)由作為此對象監(jiān)視器的所有者的線程來調(diào)用。有關(guān)線程能夠成為監(jiān)視器所有者的方法的描述,請參閱 notify 方法。
拋出: IllegalMonitorStateException – 如果當前線程不是此對象監(jiān)視器的所有者。
與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(); }
執(zhí)行一下代碼:
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");
結(jié)果為:
寫道
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
全部喚醒了。
總結(jié) 總結(jié)一下:大概有以下幾點:
1.wait(),notify(),notifyAll()都需要在擁有對象監(jiān)視器的前提下執(zhí)行,否則會出現(xiàn)異常IllegalMonitorStateException。 2.多個線程可以同時在一個對象上等待。 3.notify()將隨機喚醒一個在對象上等待的線程,沒有一個都沒有,則什么都不做。 4.notify()喚醒的線程,將在notify()線程釋放了對象監(jiān)視器以后才執(zhí)行,并不是notify了以后馬上執(zhí)行。 5.Object的這些方法與Thread的sleep、interrupt相差還是很遠的,不要混為一談了。
(###)
|