理解高併發(6).jvm對內置鎖的優化

早期的synchronized性能低下, 因爲監視器鎖monitor是依賴於底層操作系統的mutx-lock實現,當多個線程在monitor中的wait隊列中竟爭上崗時會發生線程狀態切換, 這種切換需要由操作系統的內核態轉化爲用戶態,性能比較低下。
jdk1.6對synchronized做了很多優化, 性能已經提升了很多。 具體優化策略,在對象頭中引入了輕量級鎖、偏向鎖等標識。
  • 偏向鎖
   偏向鎖是Java 6之後加入的新鎖,它是一種針對加鎖操作的優化手段,經過研究發現,在大多數情況下,鎖不僅不存在多線程競爭,而且總是由同一線程多次獲得,因此爲了減少同一線程獲取鎖(會涉及到一些CAS操作,耗時)的代價而引入偏向鎖。偏向鎖的核心思想是,如果一個線程獲得了鎖,那麼鎖就進入偏向模式,此時Mark Word 的結構也變爲偏向鎖結構,當這個線程再次請求鎖時,無需再做任何同步操作,即獲取鎖的過程,這樣就省去了大量有關鎖申請的操作,從而也就提供程序的性能。所以,對於沒有鎖競爭的場合,偏向鎖有很好的優化效果,畢竟極有可能連續多次是同一個線程申請相同的鎖。但是對於鎖競爭比較激烈的場合,偏向鎖就失效了,因爲這樣場合極有可能每次申請鎖的線程都是不相同的,因此這種場合下不應該使用偏向鎖,否則會得不償失,需要注意的是,偏向鎖失敗後,並不會立即膨脹爲重量級鎖,而是先升級爲輕量級鎖。下面我們接着瞭解輕量級鎖。
      -XX:+UseBiasedLocking

  • 輕量鎖
倘若偏向鎖失敗,虛擬機並不會立即升級爲重量級鎖,它還會嘗試使用一種稱爲輕量級鎖的優化手段(1.6之後加入的),此時Mark Word 的結構也變爲輕量級鎖的結構。輕量級鎖能夠提升程序性能的依據是“對絕大部分的鎖,在整個同步週期內都不存在競爭”,注意這是經驗數據。需要了解的是,輕量級鎖所適應的場景是線程交替執行同步塊的場合,如果存在同一時間訪問同一鎖的場合,就會導致輕量級鎖膨脹爲重量級鎖。

  • 自旋鎖
輕量級鎖失敗後,虛擬機爲了避免線程真實地在操作系統層面掛起,還會進行一項稱爲自旋鎖的優化手段。這是基於在大多數情況下,線程持有鎖的時間都不會太長,如果直接掛起操作系統層面的線程可能會得不償失,畢竟操作系統實現線程之間的切換時需要從用戶態轉換到核心態,這個狀態之間的轉換需要相對比較長的時間,時間成本相對較高,因此自旋鎖會假設在不久將來,當前的線程可以獲得鎖,因此虛擬機會讓當前想要獲取鎖的線程做幾個空循環(這也是稱爲自旋的原因),一般不會太久,可能是50個循環或100循環,在經過若干次循環後,如果得到鎖,就順利進入臨界區。如果還不能獲得鎖,那就會將線程在操作系統層面掛起,這就是自旋鎖的優化方式,這種方式確實也是可以提升效率的。最後沒辦法也就只能升級爲重量級鎖了。
1.4.2引入的概念,默認關閉,開啓參數:-XX:+UseSpinning
          1.6做了優化,默認打開,默認次數爲10次,設置循環次數:-XX:PreBlockSpin。 jdk1.6引入了自適應自旋鎖,夠智能。
         JDK1.7,自旋鎖的參數被取消,虛擬機不再支持由用戶配置自旋鎖,自旋鎖總是會執行,自旋鎖次數也由虛擬機自動調整。

  • 鎖消除(逃逸分析)
    消除鎖是虛擬機另外一種鎖的優化,這種優化更徹底,Java虛擬機在JIT編譯時(可以簡單理解爲當某段代碼即將第一次被執行時進行編譯,又稱即時編譯),通過對運行上下文的掃描,去除不可能存在共享資源競爭的鎖,通過這種方式消除沒有必要的鎖,可以節省毫無意義的請求鎖時間

  • 鎖粗化
原則上,我們在編寫代碼的時候,總是推薦將同步塊的作用範圍限制的儘量小,僅僅在共享數據的實際作用域才進行同步,這樣是爲了使得需要同步的操作儘可能變小,如果存在鎖競爭,那等待鎖的線程也能儘快的拿到鎖。大部分情況下,這兒原則是正確的,但是如果一系列的連續操作都對同一個對象反覆加鎖和解鎖,甚至鎖出現在循環體內,即使沒有線程競爭,頻繁的進行互斥操作也會導致不必要的性能損耗。

發佈了83 篇原創文章 · 獲贊 9 · 訪問量 4萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章