您好,登錄后才能下訂單哦!
本篇內(nèi)容介紹了“java的wait/notify/notifyAll方法怎么正確使用”的有關(guān)知識,在實際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領(lǐng)大家學(xué)習(xí)一下如何處理這些情況吧!希望大家仔細(xì)閱讀,能夠?qū)W有所成!
源碼中對wait方法的介紹如下:
/** * As in the one argument version, interrupts and spurious wakeups are * possible, and this method should always be used in a loop: * <pre> * synchronized (obj) { * while (<condition does not hold>) * obj.wait(); * ... // Perform action appropriate to condition * } * </pre> * This method should only be called by a thread that is the owner * of this object's monitor. See the {@code notify} method for a * description of the ways in which a thread can become the owner of * a monitor. * */ public final void wait() throws InterruptedException { wait(0); }
意思是說,在使用 wait 方法時,必須把 wait 方法寫在 synchronized 保護(hù)的 while 代碼塊中,并始終判斷執(zhí)行條件是否滿足,如果滿足就往下繼續(xù)執(zhí)行,如果不滿足就執(zhí)行 wait 方法,而在執(zhí)行 wait 方法之前,必須先持有對象的 monitor 鎖,也就是通常所說的 synchronized 鎖。
class BlockingQueue{ Queue<String> buffer = new LinkedList<>(); public void give(String data){ buffer.add(data); notify(); } public String take() throws InterruptedException { while(buffer.isEmpty()){ wait(); } return buffer.remove(); } }
在代碼中可以看到有兩個方法,give 方法負(fù)責(zé)往 buffer 中添加數(shù)據(jù),添加完之后執(zhí)行 notify 方法來喚醒之前等待的線程,而 take 方法負(fù)責(zé)檢查整個 buffer 是否為空,如果為空就進(jìn)入等待,如果不為空就取出一個數(shù)據(jù),這是典型的生產(chǎn)者消費(fèi)者的思想。
在如上所示的代碼中沒有正確使用wait()方法,那么可能出現(xiàn)什么異常呢?
首先,消費(fèi)者線程調(diào)用 take 方法并判斷 buffer.isEmpty 方法是否返回 true,若為 true 代表buffer是空的,則線程希望進(jìn)入等待,但是在線程調(diào)用 wait 方法之前(while(buffer.isEmpty())之后),就被調(diào)度器暫停了,所以此時還沒來得及執(zhí)行 wait 方法。
此時生產(chǎn)者開始運(yùn)行,執(zhí)行了整個 give 方法,它往 buffer 中添加了數(shù)據(jù),并執(zhí)行了 notify 方法,但 notify 并沒有任何效果,因為消費(fèi)者線程的 wait 方法沒來得及執(zhí)行,所以沒有線程在等待被喚醒。
此時,剛才被調(diào)度器暫停的消費(fèi)者線程回來繼續(xù)執(zhí)行 wait 方法并進(jìn)入了等待,這時消費(fèi)者便有可能陷入無窮無盡的等待,因為它錯過了剛才 give 方法內(nèi)的 notify 的喚醒。
ps:上面說的調(diào)度器暫停線程,因為在多線程下,CPU 的調(diào)度是以時間片為單位進(jìn)行分配的,每個線程都可以得到一定量的時間片。但如果線程擁有的時間片耗盡,它將會被暫停執(zhí)行并讓出 CPU 資源給其他線程。而代碼中的“判斷-執(zhí)行”不是一個原子操作,它在中間有可能被打斷,是線程不安全的,所以說有可能在線程調(diào)用 wait 方法之前這個線程就被暫停了。
class BlockingQueue{ Queue<String> buffer = new LinkedList<>(); public void give(String data){ synchronized (this){ buffer.add(data); notify(); } } public String take() throws InterruptedException { synchronized (this){ while(buffer.isEmpty()){ wait(); } return buffer.remove(); } } }
這樣就可以確保 notify 方法永遠(yuǎn)不會在 buffer.isEmpty 和 wait 方法之間被調(diào)用,提升了程序的安全性。另外,wait 方法會釋放 monitor 鎖,這也要求我們必須首先進(jìn)入到 synchronized 內(nèi)持有這把鎖。
線程可能在既沒有被notify/notifyAll,也沒有被中斷或者超時的情況下被喚醒,這種喚醒是我們不希望看到的。然在實際生產(chǎn)中,虛假喚醒發(fā)生的概率很小,但是程序依然需要保證在發(fā)生虛假喚醒的時候的正確性,所以就需要采用while循環(huán)的結(jié)構(gòu)。
while (condition does not hold) obj.wait();
這樣即便被虛假喚醒了,也會再次檢查while里面的條件,如果不滿足條件,就會繼續(xù)wait,也就消除了虛假喚醒的風(fēng)險。
它們都可以讓線程阻塞
它們都可以響應(yīng) interrupt 中斷:在等待的過程中如果收到中斷信號,都可以進(jìn)行響應(yīng),并拋出 InterruptedException 異常。
wait 方法必須在 synchronized 保護(hù)的代碼中使用,而 sleep 方法并沒有這個要求。
在同步代碼中執(zhí)行 sleep 方法時,并不會釋放 monitor 鎖,但執(zhí)行 wait 方法時會主動釋放 monitor 鎖。
sleep 方法中會要求必須定義一個時間,時間到期后會主動恢復(fù),而對于沒有參數(shù)的 wait 方法而言,意味著永久等待,直到被中斷或被喚醒才能恢復(fù),它并不會主動恢復(fù)。
wait/notify/notifyAll 被定義在 Object 類中,而 sleep 定義在 Thread 類中。
首先因為 Java 中每個對象都有一把稱之為 monitor 監(jiān)視器的鎖,每個對象都可以上鎖,在對象頭中有一個用來保存鎖信息的位置。這個鎖是對象級別的,而非線程級別的,wait/notify/notifyAll 也都是鎖級別的操作,它們的鎖屬于對象,所以把它們定義在 Object 類中是最合適,因為 Object 類是所有對象的父類。
其次,一個線程可能持有多把鎖,以便實現(xiàn)相互配合的復(fù)雜邏輯,既然我們是讓當(dāng)前線程去等待某個對象的鎖,自然應(yīng)該通過操作對象來實現(xiàn),而不是操作線程。
“java的wait/notify/notifyAll方法怎么正確使用”的內(nèi)容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業(yè)相關(guān)的知識可以關(guān)注億速云網(wǎng)站,小編將為大家輸出更多高質(zhì)量的實用文章!
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。