「Java併發編程」synchronized相關面試題總結

說說本身對於synchronized關鍵字的瞭解

synchronized關鍵字用於解決多個線程之間訪問資源的同步性,synchronized關鍵字能夠保證被它修飾的方法或者代碼塊在任意時刻只能有一個線程執行數組

值得注意的是,在Java早期,JDK1.6以前,synchronized屬於重量級鎖,效率低下。安全

緣由在於:多線程

監視器鎖【monitor】依賴於底層操做系統的Mutex Lock實現,Java的線程是映射到操做系統的原生線程之上的。若是要掛起或喚醒一個線程,都須要操做系統幫忙完成,而操做系統實現線程之間的切換時須要從用戶態轉化到內核態,須要消耗比較長的時間ide

可是,JDK1.6以後,Java官方從JVM層面對synchronized關鍵字進行了較大的優化,效率不可同日而語。主要的優化有:自旋鎖、適應性自旋鎖、鎖消除、鎖粗化、偏向鎖、輕量級鎖等技術來減小鎖操做的開銷。性能

synchronized關鍵字的三種使用
  1. 修飾實例方法:做用於當前對象實例加鎖,進入同步代碼前要得到 當前對象實例的鎖測試

  2. 修飾靜態方法: 也就是給當前類加鎖,會做用於類的全部對象實例 ,進入同步代碼前要得到 當前 class 的鎖優化

注意:靜態成員不屬於任何一個實例對象,是類成員!所以,一個線程A調用一個實例對象的非靜態synchronized方法,一個線程B調用這個實例對象的所屬類的靜態synchronized方法,是被容許的。 由於訪問靜態 synchronized 方法佔用的鎖是當前類的鎖,而訪問非靜態 synchronized 方法佔用的鎖是當前實例對象鎖this

  1. 修飾代碼塊 :給括號內配置的對象加鎖。synchronized(this|object) 表示進入同步代碼庫前要得到給定對象的鎖。synchronized(類.class) 表示進入同步代碼前要得到 當前 class 的鎖spa

synchronized關鍵字的底層原理

經過對.class文件的編譯能夠發現:操作系統

  • 同步方法經過ACC_SYNCHRONIZED修飾。

  • 代碼塊同步使用monitorenter和monitorexit兩個指令實現。

雖然二者實現細節不一樣,但其實本質上都是JVM基於進入和退出Monitor對象來實現同步,JVM的要求以下:

  • monitorenter指令會在編譯後插入到同步代碼塊的開始位置,而monitorexit則會插入到方法結束和異常處。

  • 每一個對象都有一個monitor與之關聯,且當一個monitor被持有以後,他會處於鎖定狀態。

  • 線程執行到monitorenter時,會嘗試獲取對象對應monitor的全部權。

  • 在獲取鎖時,若是對象沒被鎖定,或者當前線程已經擁有了該對象的鎖(可重進入,不會鎖死本身),將鎖計數器加一,執行monitorexit時,鎖計數器減一,計數爲零則鎖釋放。

  • 獲取對象鎖失敗,則當前線程陷入阻塞,直到對象鎖被另一個線程釋放。

JDK1.6以後對synchronized關鍵字進行的優化

優化:偏向鎖,輕量級鎖,自旋鎖,適應性自旋鎖,鎖消除,鎖粗化。

鎖主要存在的四種狀態,依次是:無鎖狀態、偏向鎖狀態、輕量級鎖狀態、重量級鎖狀態,他們會隨着競爭的激烈而逐漸升級。注意鎖能夠升級不可降級,這種策略是爲了提升得到鎖和釋放鎖的效率。

Java對象頭的組成

鎖存在於Java對象頭裏,對象頭的組成部分:

  • Mark Word:存儲對象的hashCode或鎖信息等。

  • Class Metadata Address:存儲到對象類型數據的指針。

  • Array length:數組的長度(若是當前對象是數組)

Java對象頭又存在於Java堆中,堆內存分爲三部分:對象頭,實例數據和對齊填充。

MarkWord的組成

Java對象頭的MardWord中記錄了對象和鎖的相關信息,無鎖狀態下,Java對象頭裏的Mark Word裏默認存儲對象的HashCode、分代年齡和鎖標記位。在64位的JVM中,Mark Word爲64 bit

圖片


在運行期間Mark Word裏存儲的數據會隨着鎖標誌位的變化而變化。鎖升級的功能也主要靠MarkWord中鎖標誌位是否偏向鎖標誌完成。

鎖升級的過程

鎖升級的過程:無鎖,偏向鎖,輕量級鎖,重量級鎖

圖片


偏向鎖

HotSpot的做者通過研究發現,大多數狀況下,鎖不只不存在多線程競爭,並且老是由同一線程屢次得到,爲了讓線程得到鎖的代價更低而引入了偏向鎖。

偏向鎖的適用場景

偏向鎖主要用於優化:同一線程屢次申請同一個鎖的競爭,在某些狀況下,大部分時間都是同一個線程競爭鎖資源的。

圖片


偏向鎖的加鎖

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

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

  • 若是測試失敗,則須要再測試一下Mark Word中偏向鎖的標識是否設置成1(表示當前是偏向鎖):若是沒有設置,則使用CAS競爭鎖。若是設置了,則嘗試使用CAS將對象頭的偏向鎖指向當前線程。

偏向鎖的撤銷

一旦出現其餘線程競爭鎖資源時,偏向鎖就會被撤銷。偏向鎖的撤銷可能須要等待全局安全點【在這個時間點上沒有正在執行的字節碼】。

  • 首先暫停持有該鎖的線程,而後檢查持有偏向鎖的線程是否活着,若是線程不處於活動狀態,則將對象頭設置成無鎖狀態。

  • 若是持有偏向鎖的線程仍然活着,擁有偏向鎖的棧會被執行,遍歷偏向對象的鎖記錄,棧中的鎖記錄和對象頭的Mark Word要麼從新偏向於其餘線程,要麼恢復到無鎖或者標記對象不適合做爲偏向鎖,最後喚醒在暫停的線程

偏向鎖的關閉

偏向鎖在Java 6和Java 7裏是默認啓用的,可是它在應用程序啓動幾秒鐘以後才激活,若有必要可使用JVM參數來關閉延遲:-XX:BiasedLockingStartupDelay=0。

若是說一般處於競爭狀態,能夠經過- XX:-UseBiasedLocking=false,進入輕量級鎖狀態。

輕量級鎖

如偏向鎖存在,若有另外一線程競爭鎖,且對象頭MarkWord中的線程ID與當前線程ID不一樣,則該線程將會嘗試CAS操做獲取鎖,獲取失敗,表明鎖存在競爭,偏向鎖向輕量級鎖升級

圖片


輕量級鎖的加鎖
  • 線程在執行同步塊以前,JVM會先在當前線程的棧臺中建立用於存儲鎖記錄的空間【Displaced Mark Word】,並將對象頭中的Mark Word複製到鎖記錄中

  • 而後線程嘗試使用CAS將對象頭中的Mark Word替換爲指向鎖記錄的指針。替換成功,則當前線程得到鎖。替換失敗,表示其餘線程競爭鎖,當前線程嘗試使用自旋來獲取鎖。

輕量級鎖的解鎖
  • 使用原子的CAS操做將【Displaced Mark Word】替換回對象頭。替換成功,表示沒有競爭發生。替換失敗,表示當前鎖存在競爭,鎖就會膨脹成重量級鎖。

輕量級鎖的適用場景

線程交替執行同步塊,絕大部分的鎖在整個同步週期內都不存在長時間的競爭

鎖的優缺點對比

鎖優勢缺點適用場景偏向鎖加鎖和解鎖不須要額外的消耗,和執行非同步方法比僅存在納秒級的差距。若是線程間存在鎖競爭, 會帶來額外的鎖撤銷的消耗。適用於只有一個線程訪問同步塊場景。輕量級鎖競爭的線程不會阻塞,提升了程序的響應速度。若是始終得不到所競爭的線程使用自旋會消耗CPU。追求響應時間。同步塊執行速度很是快。重量級鎖線程競爭不使用自旋,不會消耗CPU。線程阻塞,響應時間緩慢。追求吞吐量。同步塊執行速度較長。

總結
  1. JVM在JDK 1.6中引入了分級鎖機制來優化synchronized

  2. 當一個線程獲取鎖時,首先對象鎖成爲一個偏向鎖這是爲了不在同一線程重複獲取同一把鎖時,用戶態和內核態頻繁切換

  3. 若是有多個線程競爭鎖資源,鎖將會升級爲輕量級鎖這適用於在短期內持有鎖,且分鎖交替切換的場景輕量級鎖還結合了自旋鎖避免線程用戶態與內核態的頻繁切換

  4. 若是鎖競爭太激烈(自旋鎖失敗),同步鎖會升級爲重量級鎖

  5. 優化synchronized同步鎖的關鍵:減小鎖競爭應該儘可能使synchronized同步鎖處於輕量級鎖偏向鎖,這樣才能提升synchronized同步鎖的性能經常使用手段減小鎖粒度:下降鎖競爭減小鎖的持有時間,提升synchronized同步鎖在自旋時獲取鎖資源的成功率,避免升級爲重量級鎖

  6. 鎖競爭激烈時,能夠考慮禁用偏向鎖禁用自旋鎖

synchronized關鍵字與ReentrantLock的區別 共同點
  • 都是可重入鎖:本身能夠再次獲取本身的內部鎖【避免一個線程獲取鎖以後,再次嘗試獲取鎖時形成的死鎖】。同一線程每次獲取鎖,計數器加一,釋放鎖,計數器減一,計數爲0,表明徹底釋放該鎖。

不一樣點
  • synchronized依賴於JVM實現,ReentrantLock依賴於API。

  • 相比synchronized,ReentrantLock增長了一些高級功能。主要來講主要有三點:等待可中斷 : ReentrantLock提供了一種可以中斷等待鎖的線程的機制,經過 lock.lockInterruptibly() 來實現這個機制。也就是說正在等待的線程能夠選擇放棄等待,改成處理其餘事情。可實現公平鎖 : ReentrantLock能夠指定是公平鎖仍是非公平鎖。而synchronized只能是非公平鎖。所謂的公平鎖就是先等待的線程先得到鎖。ReentrantLock默認狀況是非公平的,能夠經過 ReentrantLock類的ReentrantLock(boolean fair)構造方法來制定是不是公平的。可實現選擇性通知(鎖能夠綁定多個條件): synchronized關鍵字與wait()和notify()/notifyAll()方法相結合能夠實現等待/通知機制。ReentrantLock類固然也能夠實現,可是須要藉助於Condition接口與newCondition()方法。

若是感受本文對你有幫助,點贊再看支持一下

相關文章
相關標籤/搜索