Java中notify和notifyAll的區(qū)別及何時(shí)使用
提幾個(gè)問題,從問題中去了解去學(xué)習(xí):
- 他們之間有啥區(qū)別?
- 如果我使用notify(),將通知哪個(gè)線程?
- 我怎么知道有多少線程在等待,所以我可以使用notifyAll()?
- 如何調(diào)用notify()?
- 什么是這些線程等待被通知等?
我給點(diǎn)建議:建議使用jdk8里的lock包
- java.util.concurrent.locks下的Condition 他可以支持喚醒指定的線程。
- 他只是一個(gè)接口 具體實(shí)現(xiàn)類是在AbstractQueuedSynchronizer 也就是AQS框架里的 你可以自己繼承他 或者使用 ReentrantLock里的newConditon()方法來獲取
解決下問題:
Java中notify和notifyAll的區(qū)別
Java提供了兩個(gè)方法notify和notifyAll來喚醒在某些條件下等待的線程,你可以使用它們中的任何一個(gè),但是Java中的notify和notifyAll之間存在細(xì)微差別,這使得它成為Java中流行的多線程面試問題之一。當(dāng)你調(diào)用notify時(shí),只有一個(gè)等待線程會被喚醒而且它不能保證哪個(gè)線程會被喚醒,這取決于線程調(diào)度器。雖然如果你調(diào)用notifyAll方法,那么等待該鎖的所有線程都會被喚醒,但是在執(zhí)行剩余的代碼之前,所有被喚醒的線程都將爭奪鎖定,這就是為什么在循環(huán)上調(diào)用wait,因?yàn)槿绻鄠€(gè)線程被喚醒,那么線程是將獲得鎖定將首先執(zhí)行,它可能會重置等待條件,這將迫使后續(xù)線程等待。因此,notify和notifyAll之間的關(guān)鍵區(qū)別在于notify()只會喚醒一個(gè)線程,而notifyAll方法將喚醒所有線程。
何時(shí)在Java中使用notify和notifyAll
- 如果所有線程都在等待相同的條件,并且一次只有一個(gè)線程可以從條件變?yōu)閠rue,則可以使用notify over notifyAll。
- 在這種情況下,notify是優(yōu)于notifyAll 因?yàn)閱拘阉羞@些因?yàn)槲覀冎乐挥幸粋€(gè)線程會受益而所有其他線程將再次等待,所以調(diào)用notifyAll方法只是浪費(fèi)CPU。
- 雖然這看起來很合理,但仍有一個(gè)警告,即無意中的接收者吞下了關(guān)鍵通知。通過使用notifyAll,我們確保所有收件人都會收到通知
Java中通知和notifyAll方法的示例(后序demo示例代碼 )
- 我已經(jīng)匯總了一個(gè)示例來說明當(dāng)我們在Java中調(diào)用notifyAll方法時(shí)如何通知所有線程,并且當(dāng)我們在Java中調(diào)用notify方法時(shí),只有一個(gè)Thread會被喚醒。
- 在這個(gè)例子中,如果boolean變量go為false,則三個(gè)線程將等待,記住boolean go是一個(gè)volatile變量,以便所有線程都能看到它的更新值。
- 最初三個(gè)線程WT1,WT2,WT3將等待,因?yàn)樽兞縢o為假,而一個(gè)線程N(yùn)T1將變?yōu)檎妫⑼ㄟ^調(diào)用notifyAll方法通知所有線程,或通過調(diào)用notify()方法通知一個(gè)線程。在notify()調(diào)用的情況下,無法保證哪個(gè)線程會被喚醒,您可以通過多次運(yùn)行此Java程序來查看它。
- 在notifyAll的情況下,所有線程都將被喚醒,但是它們將競爭監(jiān)視器或鎖定,并且將首先獲得鎖定的線程將完成其執(zhí)行并且重置為false將迫使其他兩個(gè)線程仍在等待。在該程序結(jié)束時(shí),將有兩個(gè)線程在等待,兩個(gè)線程包括通知線程完成。程序不會終止,因?yàn)槠渌麅蓚€(gè)線程仍在等待,并且它們不是守護(hù)程序線程。
- 實(shí)例代碼如下:以下是如何在Java中使用notify和notifyAll方法的完整代碼示例。在解釋了何時(shí)使用notify vs notifyAll方法,這個(gè)例子將闡明在Java中調(diào)用notify和notifyAll方法的效果。go!
import java.util.logging.Level; import java.util.logging.Logger; /** * Java程序演示如何在Java和Java中使用notify和notifyAll方法 *如何通知和notifyAll方法通知線程,哪個(gè)線程被喚醒等。 */ public class NotificationTest { private volatile boolean go = false; public static void main(String args[]) throws InterruptedException { final NotificationTest test = new NotificationTest(); Runnable waitTask = new Runnable(){ @Override public void run(){ try { test.shouldGo(); } catch (InterruptedException ex) { Logger.getLogger(NotificationTest.class.getName()). log(Level.SEVERE, null, ex); } System.out.println(Thread.currentThread() + " finished Execution"); } }; Runnable notifyTask = new Runnable(){ @Override public void run(){ test.go(); System.out.println(Thread.currentThread() + " finished Execution"); } }; Thread t1 = new Thread(waitTask, "WT1"); //will wait Thread t2 = new Thread(waitTask, "WT2"); //will wait Thread t3 = new Thread(waitTask, "WT3"); //will wait Thread t4 = new Thread(notifyTask,"NT1"); //will notify //starting all waiting thread t1.start(); t2.start(); t3.start(); //pause to ensure all waiting thread started successfully Thread.sleep(200); //starting notifying thread t4.start(); } /* * wait and notify can only be called from synchronized method or bock */ private synchronized void shouldGo() throws InterruptedException { while(go != true){ System.out.println(Thread.currentThread() + " is going to wait on this object"); wait(); //release lock and reacquires on wakeup System.out.println(Thread.currentThread() + " is woken up"); } go = false; //resetting condition } /* * both shouldGo() and go() are locked on current object referenced by "this" keyword */ private synchronized void go() { while (go == false){ System.out.println(Thread.currentThread() + " is going to notify all or one thread waiting on this object"); go = true; //making condition true for waiting thread //notify(); // only one out of three waiting thread WT1, WT2,WT3 will woke up notifyAll(); // all waiting thread WT1, WT2,WT3 will woke up } } }
使用notify時(shí)的輸出
Thread[WT1,5,main] is going to wait on this object
Thread[WT3,5,main] is going to wait on this object
Thread[WT2,5,main] is going to wait on this object
Thread[NT1,5,main] is going to notify all or one thread waiting on this object
Thread[WT1,5,main] is woken up
Thread[NT1,5,main] finished Execution
Thread[WT1,5,main] finished Execution
使用notifyAll時(shí)的輸出
Thread[WT1,5,main] is going to wait on this object
Thread[WT3,5,main] is going to wait on this object
Thread[WT2,5,main] is going to wait on this object
Thread[NT1,5,main] is going to notify all or one thread waiting on this object
Thread[WT2,5,main] is woken up
Thread[NT1,5,main] finished Execution
Thread[WT3,5,main] is woken up
Thread[WT3,5,main] is going to wait on this object
Thread[WT2,5,main] finished Execution
Thread[WT1,5,main] is woken up
Thread[WT1,5,main] is going to wait on this object
強(qiáng)烈建議運(yùn)行這個(gè)Java程序并理解它產(chǎn)生的輸出并嘗試?yán)斫馑3怂梨i,競爭條件和線程安全之外,線程間通信是Java中并發(fā)編程的基礎(chǔ)之一。
總結(jié):
1)如果我使用notify(),將通知哪個(gè)線程?
無法保證,ThreadScheduler將從等待該監(jiān)視器上的線程的池中選擇一個(gè)隨機(jī)線程。保證只有一個(gè)線程會被通知:(隨機(jī)性)
2) 我怎么知道有多少線程在等待,所以我可以使用notifyAll()?
它取決于程序邏輯,在編碼時(shí)需要考慮一段代碼是否可以由多個(gè)線程運(yùn)行。理解線程間通信的一個(gè)很好的例子是在Java中實(shí)現(xiàn)生產(chǎn)者 - 消費(fèi)者模式。
3) 如何調(diào)用notify()?
Wait()和notify()方法只能從synchronized方法或塊中調(diào)用,需要在其他線程正在等待的對象上調(diào)用notify方法。
4) 什么是這些線程等待被通知等?
線程等待某些條件,例如在生產(chǎn)者 - 消費(fèi)者問題中,如果共享隊(duì)列已滿,則生產(chǎn)者線程等待,如果共享隊(duì)列為空,則生成者線程等待。由于多個(gè)線程正在使用共享資源,因此它們使用wait和notify方法相互通信。
這就是Java中的notify和notifyAll方法之間的區(qū)別以及何時(shí)在Java中使用notify vs notifyAll?,F(xiàn)在,應(yīng)該能夠理解并使用notify和notifyAll方法在Java程序中進(jìn)行線程間通信。
補(bǔ)充下建議里的:lock包下的condition (demo里 是典型的生產(chǎn)者消費(fèi)者模式》》》 使用的是condition來實(shí)現(xiàn))
final Lock lock = new ReentrantLock(); //定義2組condition 對應(yīng)生產(chǎn)者消費(fèi)者 final Condition notFull = lock.newCondition(); final Condition notEmpty = lock.newCondition(); final Object[] items = new Object[100]; int putptr, takeptr, count; //在put的時(shí)候 當(dāng)數(shù)組已經(jīng)滿了的情況下 我讓線程等待 不在容納數(shù)據(jù) 當(dāng)消費(fèi)者已經(jīng)消費(fèi)了 觸發(fā)了、、 //notfull.signal() 這時(shí)候通知生產(chǎn)者 我這變已經(jīng)消費(fèi)了 你那邊可以試試了哈。 public void put(Object x) throws InterruptedException { lock.lock(); try { while (count == items.length) notFull.await(); items[putptr] = x; if (++putptr == items.length) putptr = 0; ++count; notEmpty.signal(); } finally { lock.unlock(); } } //同上 相反的理解就是了。。。 public Object take() throws InterruptedException { lock.lock(); try { while (count == 0) notEmpty.await(); Object x = items[takeptr]; if (++takeptr == items.length) takeptr = 0; --count; notFull.signal(); return x; } finally { lock.unlock(); } }
Condition因素出Object監(jiān)視器方法(wait,notify 和notifyAll)為不同的對象,以得到具有多個(gè)等待集的每個(gè)對象,通過將它們與使用任意的相結(jié)合的效果Lock的實(shí)施方式。如果Lock替換synchronized方法和語句Condition的使用,則替換Object監(jiān)視方法的使用。
條件(也稱為條件隊(duì)列或 條件變量)為一個(gè)線程提供暫停執(zhí)行(“等待”)的手段,直到另一個(gè)線程通知某個(gè)狀態(tài)條件現(xiàn)在可能為真。由于對此共享狀態(tài)信息的訪問發(fā)生在不同的線程中,因此必須對其進(jìn)行保護(hù),因此某種形式的鎖定與該條件相關(guān)聯(lián)。等待條件提供的關(guān)鍵屬性是它以原子方式釋放關(guān)聯(lián)的鎖并掛起當(dāng)前線程,就像它一樣Object.wait。
一個(gè)Condition實(shí)例本質(zhì)上綁定到一個(gè)鎖。要獲取Condition特定Lock 實(shí)例的實(shí)例,請使用其newCondition()方法。
舉個(gè)例子,假設(shè)我們有一個(gè)支持put和take方法的有界緩沖區(qū) 。如果take在空緩沖區(qū)上嘗試a ,則線程將阻塞直到某個(gè)項(xiàng)可用; 如果put在完整緩沖區(qū)上嘗試a,則線程將阻塞,直到空間可用。我們希望 在單獨(dú)的等待集中保持等待put線程和take線程,以便我們可以使用僅在緩沖區(qū)中的項(xiàng)或空間可用時(shí)通知單個(gè)線程的優(yōu)化。
也就是說 可以創(chuàng)建多個(gè)condition 每組condition 對應(yīng)你的具體的線程操作 當(dāng)你
notFull.signalAll();的時(shí)候 你喚醒的也只是你這組condition里的等待線程 對于不在這組里的notEmpty是沒有任何影響的
現(xiàn)在 你是不是可以隨心所欲的喚醒你想喚醒的線程了?
補(bǔ)充:關(guān)于notify() 和notifyAll() 一個(gè)需要注意的地方
notify() 和 notifyAll()都是喚醒其他正在等待同一個(gè)對象鎖的線程。
下面是我遇到的一個(gè)問題,記下來,免得忘了。
直接上代碼,有錯(cuò)誤的代碼:
代碼描述:有一個(gè)Caculate類,類中又一個(gè)成員變量 j,現(xiàn)在有多個(gè)線程對這個(gè)變量進(jìn)行操作。一個(gè)增加操作、一個(gè)減少操作。增加操作:當(dāng) j = 0 時(shí),j++ 。減少操作:當(dāng) j = 1 時(shí),j-- 。這兩個(gè)操作分別對應(yīng)這 add()方法 和sub()方法,都使用synchronized關(guān)鍵字。
可以直接復(fù)制拿來運(yùn)行一下
package com.zcd2; public class ThreadTest1 { public static void main(String[] args) { Caculate caculate = new Caculate(); //使用多個(gè)線程對實(shí)例caculate進(jìn)行增加操作。 for(int i = 0; i < 10; i++) { Thread1 t = new Thread1(caculate); t.start(); } //使用多個(gè)線程對實(shí)例caculate進(jìn)行減少操作。 for(int i = 0; i < 2; i++) { Thread2 t = new Thread2(caculate); t.start(); } } } //Thread1線程進(jìn)行增加操作 class Thread1 extends Thread { private Caculate caculate; public Thread1() { } public Thread1(Caculate caculate) { this.caculate = caculate; } @Override public void run() { int i = 0; //死循環(huán),手動(dòng)停止 while(true) { try { caculate.add(); } catch (InterruptedException e) { e.printStackTrace(); } i++; System.out.println("加線程執(zhí)行第 " + i + " 次"); } } } //Thread2進(jìn)行減少操作。 class Thread2 extends Thread { private Caculate caculate; public Thread2() { } public Thread2(Caculate caculate) { this.caculate = caculate; } @Override public void run() { int i = 0; //死循環(huán),手動(dòng)停止 while(true) { try { caculate.sub(); } catch (InterruptedException e) { e.printStackTrace(); } i++; System.out.println("減線程執(zhí)行第 " + i + " 次"); } } } // class Caculate { private int j = 0; //增加操作 public synchronized void add() throws InterruptedException { //當(dāng) j = 1 的時(shí)候說明不符合操作條件,要放棄對象鎖。 while(j == 1) { wait(); System.out.println(); } j++; System.out.println(j); notify(); } //減少操作 public synchronized void sub() throws InterruptedException { //當(dāng)j = 0 的時(shí)候說明不符合操作條件,放棄對象鎖 while(j == 0) { wait(); System.out.println(); } j--; System.out.println(j); notify(); } }
以上代碼并不能一直循環(huán)執(zhí)行,按道理說應(yīng)該是一直循環(huán)執(zhí)行的。
為什么呢????????
這就涉及到了notify() 和 notifyAll()的其中一個(gè)區(qū)別了。
這個(gè)區(qū)別就是:調(diào)用 notify() 方法只能隨機(jī)喚醒一個(gè)線程,調(diào)用notifyAll() 方法的喚醒所有的等待的線程。
比如這里,當(dāng)一個(gè)線程在正常執(zhí)行。。。假設(shè)這里正常執(zhí)行完一個(gè)增加操作的線程,然后調(diào)用 notify() 方法 那么它會隨機(jī)喚醒一個(gè)線程。
?、?、如果喚醒的是進(jìn)行減少操作的線程,此時(shí) j = 1,線程能夠正常執(zhí)行減少操作。
?、?、如果喚醒的是進(jìn)行增加操作的線程,此時(shí) j = 1,那么不符合增加操作的條件,他就會調(diào)用 wait() 方法。那么調(diào)用完wait()方法后程序就會發(fā)現(xiàn)已經(jīng)沒有被喚醒的線程了。唯一一個(gè)被喚醒的線程因不符合條件放棄了對象鎖,其他線程又沒有被喚醒。此時(shí)程序只能一直等到其他線程被喚醒,但是它等不到了。
解決:
把notify() 改成notifyAll() 這個(gè)問題就解決了。因?yàn)槿绻麊拘岩粋€(gè)線程,但是這個(gè)線程因不符合執(zhí)行條件而放棄對象,還有很多喚醒的線程。
所以,當(dāng)多個(gè)(兩個(gè)以上的)線程操作同一個(gè)對象的時(shí)候最好使用的notifyAll(),這樣就不會出現(xiàn)上述的問題了。
發(fā)現(xiàn)一個(gè)問題,既然使用notify()會出問題那為什么不在每個(gè)地方的使用notifyAll()呢??這二者還有其他我沒了解的區(qū)別嗎???難道使用notifyAll() 會使性能大大下降???有待解決。
到此這篇關(guān)于Java中notify和notifyAll的區(qū)別及何時(shí)使用的文章就介紹到這了,更多相關(guān)Java notify和notifyAll內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java項(xiàng)目防止SQL注入的幾種方法總結(jié)
SQL注入是比較常見的網(wǎng)絡(luò)攻擊方式之一,在客戶端在向服務(wù)器發(fā)送請求的時(shí)候,sql命令通過表單提交或者url字符串拼接傳遞到后臺持久層,最終達(dá)到欺騙服務(wù)器執(zhí)行惡意的SQL命令,下面這篇文章主要給大家總結(jié)介紹了關(guān)于Java項(xiàng)目防止SQL注入的幾種方法,需要的朋友可以參考下2023-04-04SpringBoot集成百度AI實(shí)現(xiàn)人臉識別的項(xiàng)目實(shí)踐
本文主要介紹了SpringBoot集成百度AI實(shí)現(xiàn)人臉識別的項(xiàng)目實(shí)踐,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2022-05-05Java?代碼本地設(shè)置Hadoop用戶名密碼的方法
在Hadoop環(huán)境中,通常使用Kerberos進(jìn)行身份驗(yàn)證,這篇文章主要介紹了Java?代碼本地設(shè)置Hadoop用戶名密碼的方法,需要的朋友可以參考下2024-08-08OpenJDK源碼解析之System.out.println詳解
這篇文章主要介紹了OpenJDK源碼解析之System.out.println詳解,文中有非常詳細(xì)的代碼示例,對正在學(xué)習(xí)java的小伙伴們有非常好的幫助,需要的朋友可以參考下2021-04-04Java實(shí)現(xiàn)用Mysql存取圖片操作實(shí)例
這篇文章主要介紹了Java實(shí)現(xiàn)用Mysql存取圖片操作實(shí)例,本文講解了使用BLOB類型保存和讀取圖片的代碼實(shí)例,需要的朋友可以參考下2015-06-06