synchronized 的實現原理#
synchronized 可以保證方法或者代碼塊在運行時,同一時刻只有一個方法可以進入到臨界區,同時它還可以保證共享變量的內存可見性
Java 對象頭和 monitor#
Java 對象頭和 monitor 是實現 synchronized 的基礎
對象頭#
Hotspot 虛擬機的對象頭主要包括兩部分數據:Mark Word(標記字段)、Klass Pointer(類型指針)
。其中Klass Pointer
是對象指向它的類元數據的指針,虛擬機通過這個指針來確定這個對象是哪個類的實例,Mark Word
用於存儲對象自身的運行時數據,如哈希碼(HashCode)
、GC分代年齡
、鎖狀態標誌
、線程持有的鎖
、偏向線程 ID
、偏向時間戳
等等,它是實現輕量級鎖和偏向鎖的關鍵
參考鏈接:
monitor#
聯想記憶操作系統中的鎖
作用範圍#
Java 中每一個對象都可以作為鎖,這是 synchronized 實現同步的基礎:
修飾實例方法
,鎖是當前實例對象修飾靜態方法
,鎖是當前類的 class 對象修飾代碼塊
,鎖是括號裡面的對象
同步代碼塊是使用monitorenter和monitorexit
指令實現的,同步方法依靠的是方法修飾符上的ACC_SYNCHRONIZED
實現
多線程並發時競爭鎖的情況分析#
鎖對象#
情況 1:
同一個對象在兩個線程中分別訪問該對象的兩個 synchronized 同步方法
結果:會產生互斥。
解釋:因為鎖針對的是對象,當對象調用一個 synchronized 方法時,其他同步方法需要等待其執行結束並釋放鎖後才能執行。
情況 2:
不同對象在兩個線程中調用同一個 synchronized 同步方法
結果:不會產生互斥。
解釋:因為是兩個對象,鎖針對的是對象,而不是方法,所以可以並發執行,不會互斥。形象的來說就是因為我們每個線程在調用方法的時候都是 new 一個對象,那麼就會出現兩個空間,兩把鑰匙
類鎖#
情況 1:
用類直接在兩個線程中調用兩個不同的 synchronized 同步方法
結果:會產生互斥。
解釋:對類(.class)加鎖,類對象只有一個,可以理解為任何時候都只有一個空間,裡面有 N 個房間,一把鎖,因此房間(同步方法)之間一定是互斥的。
注:上述情況和用單例模式聲明一個對象來調用非靜態方法的情況是一樣的,因為永遠就只有這一個對象。所以訪問同步方法之間一定是互斥的。
情況 2:
用一個類的靜態對象在兩個線程中調用靜態方法或非靜態方法
結果:會產生互斥。
解釋:因為是一個對象調用,同情況 1。
情況 3:
一個對象在兩個線程中分別調用一個靜態同步方法和一個非靜態同步方法
結果:不會產生互斥。
解釋:因為雖然是一個對象調用,但是兩個方法的鎖類型不同,調用的靜態方法實際上是類對象在調用,即這兩個方法產生的並不是同一個對象鎖,因此不會互斥,會並發執行
鎖優化#
jdk1.6 對鎖的實現引入了大量的優化,如自旋鎖、適應性自旋鎖、鎖消除、鎖粗化、偏向鎖、輕量級鎖等技術來減少鎖操作的開銷。 鎖主要存在四中狀態,依次是:無鎖狀態、偏向鎖狀態、輕量級鎖狀態、重量級鎖狀態,他們會隨著競爭的激烈而逐漸升級。注意鎖可以升級不可降級,這種策略是為了提高獲得鎖和釋放鎖的效率
鎖消除#
為了保證數據的完整性,我們在進行操作時需要對這部分操作進行同步控制,但是在有些情況下,JVM 檢測到不可能存在共享數據競爭,這是 JVM 會對這些同步鎖進行鎖消除,其依據是逃逸分析的數據支持
鎖粗化#
一系列的連續加鎖解鎖操作,可能會導致不必要的性能損耗,所以引入鎖粗化的概念,就是將多個連續的加鎖、解鎖操作連接在一起,擴展成一個範圍更大的鎖
自旋鎖#
線程的阻塞和喚醒需要 CPU 從用戶態轉為核心態,頻繁的阻塞和喚醒對 CPU 來說是一件負擔很重的工作,勢必會給系統的並發性能帶來很大的壓力 (聯想記憶 零拷貝
, 也是減少不必要的切換)
何謂自旋鎖? 所謂自旋鎖,就是讓該線程等待一段時間,不會被立即掛起,看持有鎖的線程是否會很快釋放鎖。怎麼等待呢?執行一段無意義的循環即可(自旋)
自旋等待不能替代阻塞,先不說對處理器數量的要求(多核,貌似現在沒有單核的處理器了),雖然它可以避免線程切換帶來的開銷,但是它佔用了處理器的時間。如果持有鎖的線程很快就釋放了鎖,那麼自旋的效率就非常好,反之,自旋的線程就會白白消耗掉處理的資源,它不會做任何有意義的工作,典型的佔著茅坑不拉屎,這樣反而會帶來性能上的浪費
適應自旋鎖#
JDK 1.6 引入了更加聰明的自旋鎖,即自適應自旋鎖。所謂自適應就意味著自旋的次數不再是固定的,它是由前一次在同一個鎖上的自旋時間及鎖的擁有者的狀態來決定。線程如果自旋成功了,那麼下次自旋的次數會更加多。反之,如果對於某個鎖,很少有自旋能夠成功的,那麼在以後要或者這個鎖的時候自旋的次數會減少甚至省略掉自旋過程,以免浪費處理器資源
輕量級鎖#
對於輕量級鎖,其性能提升的依據是 “對於絕大部分的鎖,在整個生命周期內都是不會存在競爭的”,如果打破這個依據則除了互斥的開銷外,還有額外的 CAS 操作,因此在有多線程競爭的情況下,輕量級鎖比重量級鎖更慢
偏向鎖#
輕量級鎖的加鎖解鎖操作是需要依賴多次 CAS 原子指令的,偏向鎖通過檢測 Mark Word 是否為可偏向狀態,是的話跳過 CAS 操作直接執行同步代碼塊來提升性能
重量級鎖#
重量級鎖通過對象內部的監視器(monitor)實現,其中 monitor 的本質是依賴於底層操作系統的 Mutex Lock 實現,操作系統實現線程之間的切換需要從用戶態到內核態的切換,切換成本非常高