wait()、notify、notifyAll()的使用
參考:https://www.jianshu.com/p/25e243850bd2?appinstall=0
一)、java 中對象鎖的模型
JVM會為一個使用內部鎖(synchronized)的對象維護兩個集合,Entry Set和Wait Set,即鎖池和等待池。
二)、Entry Set:*
如果線程A已經持有了對象鎖,此時如果有其他線程也想獲得該對象鎖的話,它只能進入Entry Set,並且處於線程的BLOCKED狀態。
可能進入Entey Set的線程:
1).兩個搶奪cpu的線程未搶奪到的一方。
2).notify()/notifyAll()喚醒卻未搶奪到cpu的線程。
三)、Wait Set:
如果線程A調用了wait()方法,那么線程A會釋放該對象的鎖,進入到Wait Set,並且處於線程的WAITING狀態。
可能進入wait狀態的線程:
1).調用wait()方法。
四)、Runnable狀態的轉變
Entry Set中的線程的狀態為Blocked狀態:
1).當對象鎖被釋放的時候,JVM會喚醒處於Entry Set中的某一個線程,這個線 程的狀態就從BLOCKED轉變為RUNNABLE。
Wait Set中的線程狀態為Waiting狀態:
1) .當對象的notify()方法被調用時,JVM會喚醒處於Wait Set中的某一個線程,這個線程的狀態就從WAITING轉變為RUNNABLE。
2).當notifyAll()方法被調用時,Wait Set中的全部線程會轉變為RUNNABLE狀態。所有Wait Set中被喚醒的線程會被轉移到Entry Set中。
注:只有處於Runnable狀態的線程才能去競爭鎖,獲取資源。
五)、notify()和notifyAll()的區別
notify()只喚醒一個線程,notifyAll()喚醒Wati Set的所有線程。
六)、使用notify()容易發生死鎖狀態
舉例:生產者和消費者線程
消費一進行消費,判斷buff為空,調用wait()進入等待狀態,消費者二進行消費buff為空,調用wait(),進入等待狀態,生產者一進行生產,buff滿,notify()消費者一,此時生產者二在Entry Set中搶奪到資源,判斷buff滿,進入到wait狀態,消費者一消費資源,notify(),如果喚醒了生產者,繼續生產,若此時生產者一退出了生產,喚醒了消費者二,buffer為空,消費者二進入wait狀態,此時,生產者二和消費者都進入等待狀態,沒有Runable狀態的線程,生產者2和消費者2在Wait Set中互相等待,發生死鎖。
注: 具體代碼參考https://www.jianshu.com/p/25e243850bd2?appinstall=0
七)、wait()、notify、notifyAll()要配合synchonized使用
解釋一下,這里為什么配合synchonized:
1).如果線程要調用對象的wait()方法,必須首先獲得該對象的監視器鎖,調用
wait()之后當前線程又立即釋放掉鎖,線程隨后進入WAIT_SET(等待池)中。
2).如果線程要調用對象的notify()/notifyAll()方法,也必須先獲得對象的 監視器鎖調用方法之后,立即釋放掉鎖然后處於Wait_set的線程被轉移到 Entry_set(等鎖 池)中去競爭鎖資源.。The Winner Thread,也就是 成功獲得了對象的鎖的線程,就是對象鎖的擁有者,會進入runnable狀態。
3).由於需要獲得鎖之后才能夠調用wait()/notify()方法,因此必須將它們放 到同步代碼塊中.
八)、總結
1).Jvm的內部鎖對象(synchonized)維護兩個集合Entry Set 和 Wait Set。
2).未搶占到cpu資源或被喚醒卻未搶占到cpu資源的線程會放置在Entry Set中。
3).調用wait()方法的線程進入Wait Set。
4).Entry Set集合的線程為Blocked狀態,Wait Set集合中的線程為Waiting狀態。
5).當鎖資源被釋放時,Entry Set的某一個線程狀態會變成Runnable狀態。
6).當調用notify()方法時,Wait Set的的某一線程被喚醒,由waiting狀態轉為Runable狀態,調用 notifyAll()時,Wait Set的所有線程都被喚醒,線程狀態由waiting轉為Runable狀態,並移入Entry Set中。
