wait
,notify
和notifyAll
,這些在多線程中被常常用到的保留關鍵字,在實際開發的時候不少時候卻並無被你們重視,而本文則是對這些關鍵字的使用進行描述。java
<!-- more -->git
在java中,每一個對象都有兩個池,鎖池(monitor)和等待池(waitset),每一個對象又都有
wait
、notify
、notifyAll
方法,使用它們能夠實現線程之間的通訊,只是平時用的較少。算法
notify
或notifyAll
將它喚醒鎖池: 假設T1線程
已經擁有了某個對象(注意:不是類)的鎖
,而其它的線程想要調用該對象的synchronized方法(或者synchronized塊)
,因爲這些線程在進入對象的synchronized方法
以前都須要先得到該對象的鎖的擁有權,可是該對象的鎖目前正被T1線程
擁有,因此這些線程就進入了該對象的鎖池中。微信
等待池: 假設T1線程
調用了某個對象的wait()
方法,T1線程
就會釋放該對象的鎖(由於wait()方法必須出如今synchronized中,這樣天然在執行wait()方法以前T1線程就已經擁有了該對象的鎖)
,同時T1線程
進入到了該對象的等待池中。若是有其它線程調用了相同對象的notifyAll()
方法,那麼處於該對象的等待池中的線程就會所有進入該對象的鎖池中,重新爭奪鎖的擁有權。若是另外的一個線程調用了相同對象的notify()方法,那麼僅僅有一個處於該對象的等待池中的線程(隨機)會進入該對象的鎖池.多線程
對象(注意:不是類)的鎖
。wait
和 notify
,而不是在 If 語句中synchronized
的函數或對象裏使用wait、notify和notifyAll
,否則Java虛擬機會生成 IllegalMonitorStateException
。private static int i = 0; static void product() {//生產者 System.out.println("P->" + (++i)); } static void consumer() {//消費者 System.out.println("C->" + i); } public static void main(String[] args) { new Thread(() -> { while (true) { product(); } }).start(); new Thread(() -> { while (true) { consumer(); } }).start(); } ////////////////////////日誌//////////////////////// //P->1 //P->2 //P->3 //P->4 //C->1 ////////////////////////日誌////////////////////////
分析: 從日誌中能夠看到數據會出現屢次生產或屢次消費的問題,由於在線程執行過程當中,兩個線程缺乏協做關係,都是各幹各的,T1線程
只管生產數據,無論T2線程
是否處理了。函數
private static int i = 0; private static boolean isProduction = true; static void product() {//生產者 if (isProduction) { System.out.println("P->" + (++i)); isProduction = false; } } static void consumer() {//消費者 if (!isProduction) { System.out.println("C->" + i); isProduction = true; } }
分析: 這種狀況下咱們經過維護一個變量的方式,通知對方,可是效率及其差,線程頻繁請求與判斷大大的浪費了系統資源,雖然知足了當前要求,但並不是是可選方案...oop
上文已經介紹了使用wait
和notify
的前提了,接下來看案例spa
private final static byte[] LOCK = new byte[0];//定義一個鎖對象 private static boolean isProduction = true;//消息投遞 private static int i = 0;//生產的消息 static void product() { synchronized (LOCK) {// 必須是在 synchronized中 使用 wait/notify/notifyAll try { if (isProduction) {//若是標示位爲生產狀態,則繼續生產 System.out.println("P->" + (++i)); isProduction = false; LOCK.notify();//消費者能夠消費了 } else { LOCK.wait();//說明生產出來的數據還未被消費掉 } } catch (InterruptedException e) { e.printStackTrace(); } } } static void consumer() { try { synchronized (LOCK) { if (isProduction) {//若是當前還在生產,那麼就暫停消費者線程 LOCK.wait(); } else { System.out.println("C->" + i); isProduction = true; LOCK.notify();//通知我已經消費完畢了 } } } catch (InterruptedException e) { e.printStackTrace(); } } public static void main(String[] args) { new Thread(() -> { while (true) { product(); } }).start(); new Thread(() -> { while (true) { consumer(); } }).start(); } ////////////////////////日誌//////////////////////// //P->1 //C->1 //P->2 //C->2 //..... //P->354217 //C->354217 ////////////////////////日誌////////////////////////
分析: 一切都是那麼完美,在T1線程
中,調用LOCK.wait()
將當前線程移入等待池中,並交出執行權,鎖池中的其餘線程去競爭並取得鎖的使用權(T2線程獲取
),當T2線程
消費完畢後,調用LOCK.notify()
方法通知當前對象鎖等待池中的其中一個線程(由於這裏notify是基於JVM算法而定
,由於咱們只有兩個線程,因此T1線程
會接收到T2線程
發出的通知,從而繼續生產數據。線程
問題: 雖然一對一沒有問題,但假設多個生產者多個消費者的狀況下怎麼辦呢?日誌
public static void main(String[] args) { Stream.of("P1", "P2", "P3", "P4").forEach(name -> new Thread(() -> { while (true) { product(); } }, name).start()); Stream.of("C1", "C2").forEach(name -> new Thread(() -> { while (true) { consumer(); } }, name).start()); } ////////////////////////日誌//////////////////////// //P2 -> 1 //C2 -> 1 //P2 -> 2 //C1 -> 2 //P3 -> 3 ////////////////////////日誌////////////////////////
分析: 竟然不執行了,藉助前面說過的 死鎖分析知識,咱們看看是否是發生死鎖了
結果代表,雖然沒有Found one deadlock...
字眼,可是能夠看到有個線程都被wait
住了,沒有被釋放,因此致使咱們當前沒法繼續生產消費
LOCK.notifyAll();//通知全部線程,我已經消費完畢了,大家繼續生產
分析: 這裏只修改了一句代碼,就是將consumer方法
中的notify -> notifyAll
,由通知單個線程變成通知全部在等待池
中的線程
P1 -> 1 C1 -> 1 P2 -> 2 C2 -> 2 ... P3 -> 42894 C1 -> 42894 ... P1 -> 42898 C1 -> 42898
微信公衆號:battcn
(歡迎調戲)
喜大普奔,迎來了十一國慶節....