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