本文來自: PerfMa技術社區PerfMa(笨馬網絡)官網linux
本文其實一直都想寫,由於各類緣由一直拖着沒寫,直到開公衆號的第一天,有朋友再次問到這個問題,此次讓我靜心下來準備寫下這篇文章,本文有些東西是我本身的理解,好比爲何JDK一開始要這麼設計,初衷是什麼,沒怎麼去找相關資料,因此只能談談本身的理解,因此你們看到文章以後能夠談談本身的見解,對於實現部分我倒以爲說清楚問題不大,code is here,看明白了就知道怎麼回事了。網絡
Object.wait/notify(All)你們都知道主要是協同線程處理的,你們用得也不少,大概邏輯和下面的用法差很少jvm
看到上面代碼,你會有什麼疑惑嗎?至少我會有幾個問題會問本身: 爲何進入wait和notify的時候要加synchronized鎖 既然加了synchronized鎖,那當某個線程調用了wait的時候明明還在synchronized塊裏,其餘線程怎麼進入到鎖裏去執行notify的 爲何wait方法可能會拋出InterruptedException異常 若是有多個線程都進入wait狀態,那某個線程調用notify喚醒線程時是否按照順序喚起那些wait線程 wait的線程是在某個線程執行完notify以後立馬就被喚起嗎 notifyAll又是怎麼實現全喚起的 * wait的線程是否會影響load性能
若是上面這些問題也都是你想了解的,那這篇文章或許能給你一個答案。學習
從實現上來講,這個鎖相當重要,正由於這把鎖,才能讓整個wait/notify玩轉起來,固然我以爲其實經過其餘的方式也能夠實現相似的機制,不過hotspot至少是徹底依賴這把鎖來實現wait/notify的。spa
若是要咱們來實現這種機制咱們會怎麼去作,咱們知道wait/notify是爲了線程間協做而設計的,當咱們執行wait的時候讓線程掛起,當執行notify的時候喚醒其中一個掛起的線程,那須要有個地方來保存對象和線程之間的映射關係(能夠想象一個map,key是對象,value是一個線程列表),當調用這個對象的wait方法時,將當前線程放到這個線程列表裏,當調用這個對象的notify方法時從這個線程列表裏取出一個來讓其繼續執行,這樣看來是可行的,也比較簡單,那如今的問題這種映射關係放到哪裏。而synchronized正好也是爲線程間協做而設計的,上面碰到的問題它也要解決,或許正由於這樣wait和notify的實現就直接依賴synchronzied(monitorenter/monitorexit是jvm規範裏要求要去實現的)來實現了,這只是個人理解,可能初衷不是這個緣由,這其實也是這篇文章遲遲未寫的一個緣由吧,由於我沒法取證本身的理解是對的,歡迎各位在這塊談談本身的看法。線程
這個問題其實要回答很簡單,由於在wait處理過程當中會臨時釋放同步鎖,不過須要注意的是當某個線程調用notify喚起了這個線程的時候,在wait方法退出以前會從新獲取這把鎖,只有獲取了這把鎖纔會繼續執行,想象一下,咱們知道wait的方法是被monitorenter和monitorexit包圍起來,當咱們在執行wait方法過程當中若是釋放了鎖,出來的時候又不拿鎖,那在執行到monitorexit指令的時候會發生什麼?固然這能夠作兼容,不過這實現起來仍是很奇怪的。設計
這個異常你們應該都知道,當咱們調用了某個線程的interrupt方法時,對應的線程會拋出這個異常,wait方法也不但願破壞這種規則,所以就算當前線程由於wait一直在阻塞,當某個線程但願它起來繼續執行的時候,它仍是得從阻塞態恢復過來,所以wait方法被喚醒起來的時候會去檢測這個狀態,當有線程interrupt了它的時候,它就會拋出這個異常從阻塞狀態恢復過來。code
這裏有兩點要注意: 若是被interrupt的線程只是建立了,並無start,那等他start以後進入wait態以後也是不能會恢復的 若是被interrupt的線程已經start了,在進入wait以前,若是有線程調用了其interrupt方法,那這個wait等於什麼都沒作,會直接跳出來,不會阻塞對象
這裏要分狀況: 若是是經過notify來喚起的線程,那先進入wait的線程會先被喚起來 若是是經過nootifyAll喚起的線程,默認狀況是最後進入的會先被喚起來,即LIFO的策略
其實這個你們能夠驗證一下,在notify以後寫一些邏輯,看這些邏輯是在其餘線程被喚起以前仍是以後執行,這個是個細節問題,可能你們並無關注到這個,其實hotspot裏真正的實現是退出同步塊的時候纔會去真正喚醒對應的線程,不過這個也是個默認策略,也能夠改的,在notify以後立馬喚醒相關線程。
或許你們立馬想到這個簡單,一個for循環就搞定了,不過在jvm裏沒實現這麼簡單,而是藉助了monitorexit,上面我提到了當某個線程從wait狀態恢復出來的時候,要先獲取鎖,而後再退出同步塊,因此notifyAll的實現是調用notify的線程在退出其同步塊的時候喚醒起最後一個進入wait狀態的線程,而後這個線程退出同步塊的時候繼續喚醒其倒數第二個進入wait狀態的線程,依次類推,一樣這這是一個策略的問題,jvm裏提供了挨個直接喚醒線程的參數,不過都很罕見就不提了。
這個或許是你們比較關心的話題,由於關乎系統性能問題,wait/nofity是經過jvm裏的park/unpark機制來實現的,在linux下這種機制又是經過pthread_cond_wait/pthread_cond_signal來玩的,所以當線程進入到wait狀態的時候實際上是會放棄cpu的,也就是說這類線程是不會佔用cpu資源。
一塊兒來學習吧: