JAVA線程的synchronized、wait、notify、notifyall如何配合工做
概念:鎖池、等待池、同步、資源鎖、等待、喚醒
流程描述:
1.資源鎖:多線程場景下的公共資源,資源鎖對象有鎖池和等待池兩個區域
3.鎖池,存放等待資源鎖的線程
4.等待池,存放wait狀態的線程,等待池中的線程不參與鎖競爭。
5.線程獲得資源鎖後開始工做,能夠經過wait方法臨時釋放鎖進入等待池
6.等待池中的線程只有被喚醒才能從新競爭鎖,若是競爭到鎖,接着以前的點繼續幹活html
講故事:
1.病人到醫院交了掛號費,進入候診區排隊等着醫生看病。這裏醫生就是公共資源,候診區就是醫生的鎖池。
2.病人進入診室讓醫生看病。這裏就是從鎖池中競爭到了鎖,線程能夠開始幹活。
3.醫生看了看病人的狀況,告訴病人你這病只能我師父給看,我先作個登記,你回家等着吧,等我師傅來了我通知你,病人就離開了診室。這裏醫生調用了wait方法讓病人等待,病人的家就是醫生的等待池,病人離開診室就是釋放了鎖,病人接到醫生通知後要從新到診室排隊。若是調用了wait(day)方法讓病人等待,就是告訴病人幾天後再來。
4.醫生的師傅回來了,師傅對醫生說,你讓以前等個人那些人都來吧。這裏醫生調用了notifyall方法,等待在家裏的全部病人就到醫院候診區排隊,誰先排上隊看病人的本事。
5.醫生的師傅回來了,師傅對醫生說,你隨機挑一個病人來吧。這裏醫生調用了notify方法,等待在家裏病人其中一個運氣比較好被選中了,他就到候診區來排隊了。java
注:安全
1.線程自己屬性:sleep、join、yield方法多線程
2.多線程同步使用的Object屬性:wait、notify、notifyAll是Object的方法(synchronized要針對資源object起做用,因此也要經過調用資源object的wait和notify方法實現鎖的等待),結論wait和notify以及notifyall必需要在synchronized代碼塊中使用,不然Java虛擬機會生成 IllegalMonitorStateException。由於wait的目的是爲了進入等待並釋放鎖。dom
3.在多線程同步的時候使用等待池和鎖池ide
4.在單線程運行的時候可使用sleep、yield釋放CPU控制權,進去就緒池函數
5.join是一個特例,thread.join()是要當前線程等待,直到thread運行完成。thread.join就是讓「當前線程」(正在擁有CPU控制權的主線程)等待,thread線程運行結束時會經過native方法調用notify讓主線程繼續工做。this
實例:spa
下面咱們提供一個使用wait和notify的範例程序。在這個程序裏,咱們使用了上文所述的一些代碼規範。咱們有兩個線程,分別名爲PRODUCER(生產者)和CONSUMER(消費者),他們分別繼承了了Producer和Consumer類,而Producer和Consumer都繼承了Thread類。Producer和Consumer想要實現的代碼邏輯都在run()函數內。Main線程開始了生產者和消費者線程,並聲明瞭一個LinkedList做爲緩衝區隊列(在Java中,LinkedList實現了隊列的接口)。生產者在無限循環中持續往LinkedList裏插入隨機整數直到LinkedList滿。咱們在while(queue.size == maxSize)循環語句中檢查這個條件。請注意到咱們在作這個檢查條件以前已經在隊列對象上使用了synchronized關鍵詞,於是其它線程不能在咱們檢查條件時改變這個隊列。若是隊列滿了,那麼PRODUCER線程會在CONSUMER線程消耗掉隊列裏的任意一個整數,並用notify來通知PRODUCER線程以前持續等待。在咱們的例子中,wait和notify都是使用在同一個共享對象上的。線程
import java.util.LinkedList; import java.util.Queue; import java.util.Random; /** * Simple Java program to demonstrate How to use wait, notify and notifyAll() * method in Java by solving producer consumer problem. * * @author Javin Paul */ public class ProducerConsumerInJava { public static void main(String args[]) { System.out.println("How to use wait and notify method in Java"); System.out.println("Solving Producer Consumper Problem"); Queue<Integer> buffer = new LinkedList<>(); int maxSize = 10; Thread producer = new Producer(buffer, maxSize, "PRODUCER"); Thread consumer = new Consumer(buffer, maxSize, "CONSUMER"); producer.start(); consumer.start(); } } /** * Producer Thread will keep producing values for Consumer * to consumer. It will use wait() method when Queue is full * and use notify() method to send notification to Consumer * Thread. * * @author WINDOWS 8 * */ class Producer extends Thread { private Queue<Integer> queue; private int maxSize; public Producer(Queue<Integer> queue, int maxSize, String name){ super(name); this.queue = queue; this.maxSize = maxSize; } @Override public void run() { while (true) { synchronized (queue) { while (queue.size() == maxSize) { try { System.out .println("Queue is full, " + "Producer thread waiting for " + "consumer to take something from queue"); queue.wait(); } catch (Exception ex) { ex.printStackTrace(); } } Random random = new Random(); int i = random.nextInt(); System.out.println("Producing value : " + i); queue.add(i); queue.notifyAll(); } } } } /** * Consumer Thread will consumer values form shared queue. * It will also use wait() method to wait if queue is * empty. It will also use notify method to send * notification to producer thread after consuming values * from queue. * * @author WINDOWS 8 * */ class Consumer extends Thread { private Queue<Integer> queue; private int maxSize; public Consumer(Queue<Integer> queue, int maxSize, String name){ super(name); this.queue = queue; this.maxSize = maxSize; } @Override public void run() { while (true) { synchronized (queue) { while (queue.isEmpty()) { System.out.println("Queue is empty," + "Consumer thread is waiting" + " for producer thread to put something in queue"); try { queue.wait(); } catch (Exception ex) { ex.printStackTrace(); } } System.out.println("Consuming value : " + queue.remove()); queue.notifyAll(); } } } }
爲了更好地理解這個程序,我建議你在debug模式裏跑這個程序。一旦你在debug模式下啓動程序,它會中止在PRODUCER或者CONSUMER線程上,取決於哪一個線程佔據了CPU。由於兩個線程都有wait()的條件,它們必定會中止,而後你就能夠跑這個程序而後看發生什麼了(頗有可能它就會輸出咱們以上展現的內容)。你也可使用Eclipse裏的Step into和Step over按鈕來更好地理解多線程間發生的事情。
notify()和notifyAll()的本質區別
notify()和notifyAll()都是Object對象用於通知處在等待該對象的線程的方法。二者的最大區別在於:
notifyAll使全部原來在該對象上等待被notify的全部線程通通退出wait的狀態,變成等待該對象上的鎖,一旦該對象被解鎖,他們就會去競爭。
notify則文明得多,它只是選擇一個wait狀態線程進行通知,並使它得到該對象上的鎖,但不驚動其餘一樣在等待被該對象notify的線程們,當第一個線程運行完畢之後釋放對象上的鎖此時若是該對象沒有再次使用notify語句,則即使該對象已經空閒,其餘wait狀態等待的線程因爲沒有獲得該對象的通知,繼續處在wait狀態,直到這個對象發出一個notify或notifyAll,它們等待的是被notify或notifyAll,而不是鎖。
1. 你可使用wait和notify函數來實現線程間通訊。你能夠用它們來實現多線程(>3)之間的通訊。
2. 永遠在synchronized的函數或對象裏使用wait、notify和notifyAll,否則Java虛擬機會生成 IllegalMonitorStateException。
3. 永遠在while循環裏而不是if語句下使用wait。這樣,循環會在線程睡眠先後都檢查wait的條件,並在條件實際上並未改變的狀況下處理喚醒通知。
4. 永遠在多線程間共享的對象(在生產者消費者模型裏即緩衝區隊列)上使用wait。
5. 基於前文說起的理由,更傾向用 notifyAll(),而不是 notify()。
6.線程sleep結束後不是馬上獲取CPU時間片的,要從新等待CPU的時間片分配。
這是關於Java裏如何使用wait, notify和notifyAll的全部重點啦。你應該只在你知道本身要作什麼的狀況下使用這些函數,否則Java裏還有不少其它的用來解決同步問題的方案。例如,若是你想使用生產者消費者模型的話,你也可使用BlockingQueue,它會幫你處理全部的線程安全問題和流程控制。若是你想要某一個線程等待另外一個線程作出反饋再繼續運行,你也可使用CycliBarrier或者CountDownLatch。若是你只是想保護某一個資源的話,你也可使用Semaphore。