synchronize原理

 

synchronized的三種應用方式

一. 修飾實例方法,做用於當前實例加鎖,進入同步代碼前要得到當前實例的鎖。數組

二. 修飾靜態方法,做用於當前類對象加鎖,進入同步代碼前要得到當前類對象的鎖。安全

三. 修飾代碼塊,指定加鎖對象,對給定對象加鎖,進入同步代碼庫前要得到給定對象。多線程

synchronized的字節碼指令

  synchronized同步塊使用了monitorenter和monitorexit指令實現同步,這兩個指令,本質上都是對一個對象的監視器(monitor)進行獲取,這個過程是排他的,也就是說同一時刻只能有一個線程獲取到由synchronized所保護對象的監視器。jvm

  線程執行到monitorenter指令時,會嘗試獲取對象所對應的monitor全部權,也就是嘗試獲取對象的鎖,而執行monitorexit,就是釋放monitor的全部權。工具

synchronized的鎖的原理

  兩個重要的概念:一個是對象頭,另外一個是monitor。佈局

Java對象頭

  在Hotspot虛擬機中,對象在內存中的佈局分爲三塊區域:對象頭(Mark Word、Class Metadata Address)、實例數據和對齊填充;Java對象頭是實現synchronized的鎖對象的基礎。通常而言,synchronized使用的鎖對象是存儲在Java對象頭裏。它是輕量級鎖和偏向鎖的關鍵。性能

Mark Word

  Mark Word用於存儲對象自身的運行時數據,如哈希碼(HashCode)、GC分代年齡、鎖狀態標誌、線程持有的
鎖、偏向線程 ID、偏向時間戳等等。Java對象頭通常佔有兩個機器碼(在32位虛擬機中,1個機器碼等於4字節,
也就是32bit)。學習

 

 

Class Metadata Address

  類型指針,便是對象指向它的類的元數據的指針,虛擬機經過這個指針來肯定這個對象是哪一個類的實例。測試

Array length

若是對象是一個Java數組,那在對象頭中還必須有一塊用於記錄數組長度的數據。優化

Monitor

  Monitor是一個同步工具,它內置於每個Object對象中,至關於一個許可證。拿到許可證便可以進行操做,沒有拿到則須要阻塞等待。

  在hotspot虛擬機中,經過ObjectMonitor類來實現monitor。

 

 

synchronized鎖的優化

  jdk1.6之後對synchronized的鎖進行了優化,引入了偏向鎖、輕量級鎖,鎖的級別從低到高逐步升級: 

  無鎖->偏向鎖->輕量級鎖->重量級鎖

自旋鎖與自適應自旋

  線程的掛起和恢復會極大的影響開銷。而且jdk官方人員發現,不少線程在等待鎖的時候,在很短的一段時間就得到了鎖,因此它們在線程等待的時候,並不須要把線程掛起,而是讓他無目的的循環,通常設置10次。這樣就避免了線程切換的開銷,極大的提高了性能。

而適應性自旋,是賦予了自旋一種學習能力,它並不固定自旋10次一下。他能夠根據它前面線程的自旋狀況,從而調整它的自旋,甚至是不通過自旋而直接掛起。

鎖消除

  對不會存在線程安全的鎖進行消除。

鎖粗化

  若是jvm檢測到有一串零碎的操做都對同一個對象加鎖,將會把鎖粗化到整個操做外部,如循環體。

偏向鎖

  多數狀況下,鎖不只不存在多線程競爭,並且老是由同一線程屢次得到,爲了讓其得到鎖的代價更低而引入了偏向鎖。

  當一個線程訪問同步塊並獲取鎖時,會在對象頭和棧幀中的鎖記錄裏存儲鎖偏向的線程ID,之後該線程在進入和退出同步塊時不須要進行CAS操做來加鎖和解鎖,只需簡單地測試一下對象頭的Mark Word裏是否存儲着指向當前線程的偏向鎖。

  若是測試成功,表示線程已經得到了鎖。

  若是測試失敗,則須要再測試一下Mark Word中偏向鎖的標識是否設置成01(表示當前是偏向鎖)。

  若是沒有設置,則使用CAS競爭鎖。

  若是設置了,則嘗試使用CAS將對象頭的偏向鎖指向當前線程。

輕量級鎖

  引入輕量級鎖的主要目的是在多線程競爭不激烈的狀況下,經過CAS競爭鎖,減小傳統的重量級鎖使用操做系統互斥量產生的性能消耗。

重量級鎖

  重量級鎖經過對象內部的監視器(monitor)實現,其中monitor的本質是依賴於底層操做系統的Mutex Lock實現,操做系統實現線程之間的切換須要從用戶態到內核態的切換,切換成本很是高。

鎖升級

  偏向鎖升級輕量級鎖:當一個對象持有偏向鎖,一旦第二個線程訪問這個對象,若是產生競爭,偏向鎖升級爲輕量級鎖。

  輕量級鎖升級重量級鎖:通常兩個線程對於同一個鎖的操做都會錯開,或者說稍微等待一下(自旋),另外一個線程就會釋放鎖。可是當自旋超過必定的次數,或者一個線程在持有鎖,一個在自旋,又有第三個來訪時,輕量級鎖膨脹爲重量級鎖,重量級鎖使除了擁有鎖的線程之外的線程都阻塞,防止CPU空轉。

wait和notify的原理

  調用wait方法,首先會獲取監視器鎖,得到成功之後,會讓當前線程進入等待狀態進入等待隊列而且釋放鎖。

  當其餘線程調用notify後,會選擇從等待隊列中喚醒任意一個線程,而執行完notify方法之後,並不會立馬喚醒線程,緣由是當前的線程仍然持有這把鎖,處於等待狀態的線程沒法得到鎖。必需要等到當前的線程執行完按monitorexit指令之後,也就是鎖被釋放之後,處於等待隊列中的線程就能夠開始競爭鎖了。

wait和notify爲何須要在synchronized裏面?

  wait方法的語義有兩個,一個是釋放當前的對象鎖、另外一個是使得當前線程進入阻塞隊列,而這些操做都和監視器是相關的,因此wait必需要得到一個監視器鎖。

而對於notify來講也是同樣,它是喚醒一個線程,既然要去喚醒,首先得知道它在哪裏,因此就必需要找到這個對象獲取到這個對象的鎖,而後到這個對象的等待隊列中去喚醒一個線程。

相關文章
相關標籤/搜索