Java中notify和notifyAll的區(qū)別及何時使用
提幾個問題,從問題中去了解去學習:
- 他們之間有啥區(qū)別?
- 如果我使用notify(),將通知哪個線程?
- 我怎么知道有多少線程在等待,所以我可以使用notifyAll()?
- 如何調用notify()?
- 什么是這些線程等待被通知等?
我給點建議:建議使用jdk8里的lock包
- java.util.concurrent.locks下的Condition 他可以支持喚醒指定的線程。
- 他只是一個接口 具體實現類是在AbstractQueuedSynchronizer 也就是AQS框架里的 你可以自己繼承他 或者使用 ReentrantLock里的newConditon()方法來獲取
解決下問題:
Java中notify和notifyAll的區(qū)別
Java提供了兩個方法notify和notifyAll來喚醒在某些條件下等待的線程,你可以使用它們中的任何一個,但是Java中的notify和notifyAll之間存在細微差別,這使得它成為Java中流行的多線程面試問題之一。當你調用notify時,只有一個等待線程會被喚醒而且它不能保證哪個線程會被喚醒,這取決于線程調度器。雖然如果你調用notifyAll方法,那么等待該鎖的所有線程都會被喚醒,但是在執(zhí)行剩余的代碼之前,所有被喚醒的線程都將爭奪鎖定,這就是為什么在循環(huán)上調用wait,因為如果多個線程被喚醒,那么線程是將獲得鎖定將首先執(zhí)行,它可能會重置等待條件,這將迫使后續(xù)線程等待。因此,notify和notifyAll之間的關鍵區(qū)別在于notify()只會喚醒一個線程,而notifyAll方法將喚醒所有線程。
何時在Java中使用notify和notifyAll
- 如果所有線程都在等待相同的條件,并且一次只有一個線程可以從條件變?yōu)閠rue,則可以使用notify over notifyAll。
- 在這種情況下,notify是優(yōu)于notifyAll 因為喚醒所有這些因為我們知道只有一個線程會受益而所有其他線程將再次等待,所以調用notifyAll方法只是浪費CPU。
- 雖然這看起來很合理,但仍有一個警告,即無意中的接收者吞下了關鍵通知。通過使用notifyAll,我們確保所有收件人都會收到通知
Java中通知和notifyAll方法的示例(后序demo示例代碼 )
- 我已經匯總了一個示例來說明當我們在Java中調用notifyAll方法時如何通知所有線程,并且當我們在Java中調用notify方法時,只有一個Thread會被喚醒。
- 在這個例子中,如果boolean變量go為false,則三個線程將等待,記住boolean go是一個volatile變量,以便所有線程都能看到它的更新值。
- 最初三個線程WT1,WT2,WT3將等待,因為變量go為假,而一個線程NT1將變?yōu)檎?,并通過調用notifyAll方法通知所有線程,或通過調用notify()方法通知一個線程。在notify()調用的情況下,無法保證哪個線程會被喚醒,您可以通過多次運行此Java程序來查看它。
- 在notifyAll的情況下,所有線程都將被喚醒,但是它們將競爭監(jiān)視器或鎖定,并且將首先獲得鎖定的線程將完成其執(zhí)行并且重置為false將迫使其他兩個線程仍在等待。在該程序結束時,將有兩個線程在等待,兩個線程包括通知線程完成。程序不會終止,因為其他兩個線程仍在等待,并且它們不是守護程序線程。
- 實例代碼如下:以下是如何在Java中使用notify和notifyAll方法的完整代碼示例。在解釋了何時使用notify vs notifyAll方法,這個例子將闡明在Java中調用notify和notifyAll方法的效果。go!
import java.util.logging.Level; import java.util.logging.Logger; /** * Java程序演示如何在Java和Java中使用notify和notifyAll方法 *如何通知和notifyAll方法通知線程,哪個線程被喚醒等。 */ 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時的輸出
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時的輸出
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
強烈建議運行這個Java程序并理解它產生的輸出并嘗試理解它。除了死鎖,競爭條件和線程安全之外,線程間通信是Java中并發(fā)編程的基礎之一。
總結:
1)如果我使用notify(),將通知哪個線程?
無法保證,ThreadScheduler將從等待該監(jiān)視器上的線程的池中選擇一個隨機線程。保證只有一個線程會被通知:(隨機性)
2) 我怎么知道有多少線程在等待,所以我可以使用notifyAll()?
它取決于程序邏輯,在編碼時需要考慮一段代碼是否可以由多個線程運行。理解線程間通信的一個很好的例子是在Java中實現生產者 - 消費者模式。
3) 如何調用notify()?
Wait()和notify()方法只能從synchronized方法或塊中調用,需要在其他線程正在等待的對象上調用notify方法。
4) 什么是這些線程等待被通知等?
線程等待某些條件,例如在生產者 - 消費者問題中,如果共享隊列已滿,則生產者線程等待,如果共享隊列為空,則生成者線程等待。由于多個線程正在使用共享資源,因此它們使用wait和notify方法相互通信。
這就是Java中的notify和notifyAll方法之間的區(qū)別以及何時在Java中使用notify vs notifyAll?,F在,應該能夠理解并使用notify和notifyAll方法在Java程序中進行線程間通信。
補充下建議里的:lock包下的condition (demo里 是典型的生產者消費者模式》》》 使用的是condition來實現)
final Lock lock = new ReentrantLock(); //定義2組condition 對應生產者消費者 final Condition notFull = lock.newCondition(); final Condition notEmpty = lock.newCondition(); final Object[] items = new Object[100]; int putptr, takeptr, count; //在put的時候 當數組已經滿了的情況下 我讓線程等待 不在容納數據 當消費者已經消費了 觸發(fā)了、、 //notfull.signal() 這時候通知生產者 我這變已經消費了 你那邊可以試試了哈。 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)為不同的對象,以得到具有多個等待集的每個對象,通過將它們與使用任意的相結合的效果Lock的實施方式。如果Lock替換synchronized方法和語句Condition的使用,則替換Object監(jiān)視方法的使用。
條件(也稱為條件隊列或 條件變量)為一個線程提供暫停執(zhí)行(“等待”)的手段,直到另一個線程通知某個狀態(tài)條件現在可能為真。由于對此共享狀態(tài)信息的訪問發(fā)生在不同的線程中,因此必須對其進行保護,因此某種形式的鎖定與該條件相關聯。等待條件提供的關鍵屬性是它以原子方式釋放關聯的鎖并掛起當前線程,就像它一樣Object.wait。
一個Condition實例本質上綁定到一個鎖。要獲取Condition特定Lock 實例的實例,請使用其newCondition()方法。
舉個例子,假設我們有一個支持put和take方法的有界緩沖區(qū) 。如果take在空緩沖區(qū)上嘗試a ,則線程將阻塞直到某個項可用; 如果put在完整緩沖區(qū)上嘗試a,則線程將阻塞,直到空間可用。我們希望 在單獨的等待集中保持等待put線程和take線程,以便我們可以使用僅在緩沖區(qū)中的項或空間可用時通知單個線程的優(yōu)化。
也就是說 可以創(chuàng)建多個condition 每組condition 對應你的具體的線程操作 當你
notFull.signalAll();的時候 你喚醒的也只是你這組condition里的等待線程 對于不在這組里的notEmpty是沒有任何影響的
現在 你是不是可以隨心所欲的喚醒你想喚醒的線程了?
補充:關于notify() 和notifyAll() 一個需要注意的地方
notify() 和 notifyAll()都是喚醒其他正在等待同一個對象鎖的線程。
下面是我遇到的一個問題,記下來,免得忘了。
直接上代碼,有錯誤的代碼:
代碼描述:有一個Caculate類,類中又一個成員變量 j,現在有多個線程對這個變量進行操作。一個增加操作、一個減少操作。增加操作:當 j = 0 時,j++ 。減少操作:當 j = 1 時,j-- 。這兩個操作分別對應這 add()方法 和sub()方法,都使用synchronized關鍵字。
可以直接復制拿來運行一下
package com.zcd2; public class ThreadTest1 { public static void main(String[] args) { Caculate caculate = new Caculate(); //使用多個線程對實例caculate進行增加操作。 for(int i = 0; i < 10; i++) { Thread1 t = new Thread1(caculate); t.start(); } //使用多個線程對實例caculate進行減少操作。 for(int i = 0; i < 2; i++) { Thread2 t = new Thread2(caculate); t.start(); } } } //Thread1線程進行增加操作 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),手動停止 while(true) { try { caculate.add(); } catch (InterruptedException e) { e.printStackTrace(); } i++; System.out.println("加線程執(zhí)行第 " + i + " 次"); } } } //Thread2進行減少操作。 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),手動停止 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 { //當 j = 1 的時候說明不符合操作條件,要放棄對象鎖。 while(j == 1) { wait(); System.out.println(); } j++; System.out.println(j); notify(); } //減少操作 public synchronized void sub() throws InterruptedException { //當j = 0 的時候說明不符合操作條件,放棄對象鎖 while(j == 0) { wait(); System.out.println(); } j--; System.out.println(j); notify(); } }
以上代碼并不能一直循環(huán)執(zhí)行,按道理說應該是一直循環(huán)執(zhí)行的。
為什么呢????????
這就涉及到了notify() 和 notifyAll()的其中一個區(qū)別了。
這個區(qū)別就是:調用 notify() 方法只能隨機喚醒一個線程,調用notifyAll() 方法的喚醒所有的等待的線程。
比如這里,當一個線程在正常執(zhí)行。。。假設這里正常執(zhí)行完一個增加操作的線程,然后調用 notify() 方法 那么它會隨機喚醒一個線程。
?、?、如果喚醒的是進行減少操作的線程,此時 j = 1,線程能夠正常執(zhí)行減少操作。
?、?、如果喚醒的是進行增加操作的線程,此時 j = 1,那么不符合增加操作的條件,他就會調用 wait() 方法。那么調用完wait()方法后程序就會發(fā)現已經沒有被喚醒的線程了。唯一一個被喚醒的線程因不符合條件放棄了對象鎖,其他線程又沒有被喚醒。此時程序只能一直等到其他線程被喚醒,但是它等不到了。
解決:
把notify() 改成notifyAll() 這個問題就解決了。因為如果喚醒一個線程,但是這個線程因不符合執(zhí)行條件而放棄對象,還有很多喚醒的線程。
所以,當多個(兩個以上的)線程操作同一個對象的時候最好使用的notifyAll(),這樣就不會出現上述的問題了。
發(fā)現一個問題,既然使用notify()會出問題那為什么不在每個地方的使用notifyAll()呢??這二者還有其他我沒了解的區(qū)別嗎???難道使用notifyAll() 會使性能大大下降???有待解決。
到此這篇關于Java中notify和notifyAll的區(qū)別及何時使用的文章就介紹到這了,更多相關Java notify和notifyAll內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
OpenJDK源碼解析之System.out.println詳解
這篇文章主要介紹了OpenJDK源碼解析之System.out.println詳解,文中有非常詳細的代碼示例,對正在學習java的小伙伴們有非常好的幫助,需要的朋友可以參考下2021-04-04