Java中的鎖[原理、鎖優化、CAS、AQS]

本文作者:景小財
作者簡介:美團外賣活動業務負責人

1、為什么要用鎖?

鎖-是為了解決并發操作引起的臟讀、數據不一致的問題。

2、鎖實現的基本原理

2.1、volatile

Java編程語言允許線程訪問共享變量, 為了確保共享變量能被準確和一致地更新,線程應該確保通過排他鎖單獨獲得這個變量。Java語言提供了volatile,在某些情況下比鎖要更加方便。

volatile在多處理器開發中保證了共享變量的“ 可見性”。可見性的意思是當一個線程修改一個共享變量時,另外一個線程能讀到這個修改的值。

image.png

結論:如果volatile變量修飾符使用恰當的話,它比synchronized的使用和執行成本更低,因為它不會引起線程上下文的切換和調度。

2.2、synchronized

synchronized通過鎖機制實現同步。

先來看下利用synchronized實現同步的基礎:Java中的每一個對象都可以作為鎖。

具體表現為以下3種形式。

  • 對于普通同步方法,鎖是當前實例對象。
  • 對于靜態同步方法,鎖是當前類的Class對象。
  • 對于同步方法塊,鎖是Synchonized括號里配置的對象。

當一個線程試圖訪問同步代碼塊時,它首先必須得到鎖,退出或拋出異常時必須釋放鎖。

2.2.1 synchronized實現原理

synchronized是基于Monitor來實現同步的。

Monitor從兩個方面來支持線程之間的同步:

  • 互斥執行
  • 協作

1、Java 使用對象鎖 ( 使用 synchronized 獲得對象鎖 ) 保證工作在共享的數據集上的線程互斥執行。

2、使用 notify/notifyAll/wait 方法來協同不同線程之間的工作。

3、Class和Object都關聯了一個Monitor。

Monitor 的工作機理
  • 線程進入同步方法中。
  • 為了繼續執行臨界區代碼,線程必須獲取 Monitor 鎖。如果獲取鎖成功,將成為該監視者對象的擁有者。任一時刻內,監視者對象只屬于一個活動線程(The Owner)
  • 擁有監視者對象的線程可以調用 wait() 進入等待集合(Wait Set),同時釋放監視鎖,進入等待狀態。
  • 其他線程調用 notify() / notifyAll() 接口喚醒等待集合中的線程,這些等待的線程需要重新獲取監視鎖后才能執行 wait() 之后的代碼。
  • 同步方法執行完畢了,線程退出臨界區,并釋放監視鎖。

參考文檔:https://www.ibm.com/developerworks/cn/java/j-lo-synchronized

2.2.2 synchronized具體實現

1、同步代碼塊采用monitorenter、monitorexit指令顯式的實現。

2、同步方法則使用ACC_SYNCHRONIZED標記符隱式的實現。

通過實例來看看具體實現:

public class SynchronizedTest {
 
    public synchronized void method1(){
        System.out.println("Hello World!");
    }
 
    public  void method2(){
        synchronized (this){
            System.out.println("Hello World!");
        }
    }
}

javap編譯后的字節碼如下:

image.png

monitorenter

每一個對象都有一個monitor,一個monitor只能被一個線程擁有。當一個線程執行到monitorenter指令時會嘗試獲取相應對象的monitor,獲取規則如下:

  • 如果monitor的進入數為0,則該線程可以進入monitor,并將monitor進入數設置為1,該線程即為monitor的擁有者。
  • 如果當前線程已經擁有該monitor,只是重新進入,則進入monitor的進入數加1,所以synchronized關鍵字實現的鎖是可重入的鎖。
  • 如果monitor已被其他線程擁有,則當前線程進入阻塞狀態,直到monitor的進入數為0,再重新嘗試獲取monitor。

monitorexit

只有擁有相應對象的monitor的線程才能執行monitorexit指令。每執行一次該指令monitor進入數減1,當進入數為0時當前線程釋放monitor,此時其他阻塞的線程將可以嘗試獲取該monitor。

2.2.3 鎖存放的位置

鎖標記存放在Java對象頭的Mark Word中。

Java對象頭長度
32位JVM Mark Word 結構
32位JVM Mark Word 狀態變化
64位JVM Mark Word 結構

2.2.3 synchronized的鎖優化

JavaSE1.6為了減少獲得鎖和釋放鎖帶來的性能消耗,引入了“偏向鎖”和“輕量級鎖”。

在JavaSE1.6中,鎖一共有4種狀態,級別從低到高依次是:無鎖狀態、偏向鎖狀態、輕量級鎖狀態和重量級鎖狀態,這幾個狀態會隨著競爭情況逐漸升級。

鎖可以升級但不能降級,意味著偏向鎖升級成輕量級鎖后不能降級成偏向鎖。這種鎖升級卻不能降級的策略,目的是為了提高獲得鎖和釋放鎖的效率。

偏向鎖:

無鎖競爭的情況下為了減少鎖競爭的資源開銷,引入偏向鎖。

image.png

輕量級鎖:

輕量級鎖所適應的場景是線程交替執行同步塊的情況。

image.png

鎖粗化(Lock Coarsening):也就是減少不必要的緊連在一起的unlock,lock操作,將多個連續的鎖擴展成一個范圍更大的鎖。

鎖消除(Lock Elimination):鎖削除是指虛擬機即時編譯器在運行時,對一些代碼上要求同步,但是被檢測到不可能存在共享數據競爭的鎖進行削除。

適應性自旋(Adaptive Spinning):自適應意味著自旋的時間不再固定了,而是由前一次在同一個鎖上的自旋時間及鎖的擁有者的狀態來決定。如果在同一個鎖對象上,自旋等待剛剛成功獲得過鎖,并且持有鎖的線程正在運行中,那么虛擬機就會認為這次自旋也很有可能再次成功,進而它將允許自旋等待持續相對更長的時間,比如100個循環。另一方面,如果對于某個鎖,自旋很少成功獲得過,那在以后要獲取這個鎖時將可能省略掉自旋過程,以避免浪費處理器資源。

2.2.4 鎖的優缺點對比

image.png

2.3、CAS

CAS,在Java并發應用中通常指CompareAndSwap或CompareAndSet,即比較并交換。

1、CAS是一個原子操作,它比較一個內存位置的值并且只有相等時修改這個內存位置的值為新的值,保證了新的值總是基于最新的信息計算的,如果有其他線程在這期間修改了這個值則CAS失敗。CAS返回是否成功或者內存位置原來的值用于判斷是否CAS成功。

2、JVM中的CAS操作是利用了處理器提供的CMPXCHG指令實現的。

優點:

  • 競爭不大的時候系統開銷小。

缺點:

  • 循環時間長開銷大。
  • ABA問題。
  • 只能保證一個共享變量的原子操作。

3、Java中的鎖實現

3.1、隊列同步器(AQS)

隊列同步器AbstractQueuedSynchronizer(以下簡稱同步器),是用來構建鎖或者其他同步組件的基礎框架。

3.1.1、它使用了一個int成員變量表示同步狀態。

image.png

3.1.2、通過內置的FIFO雙向隊列來完成獲取鎖線程的排隊工作。

  • 同步器包含兩個節點類型的應用,一個指向頭節點,一個指向尾節點,未獲取到鎖的線程會創建節點線程安全(compareAndSetTail)的加入隊列尾部。同步隊列遵循FIFO,首節點是獲取同步狀態成功的節點。


    image.png
  • 未獲取到鎖的線程將創建一個節點,設置到尾節點。如下圖所示:

image.png
  • 首節點的線程在釋放鎖時,將會喚醒后繼節點。而后繼節點將會在獲取鎖成功時將自己設置為首節點。如下圖所示:


    image.png

3.1.3、獨占式/共享式鎖獲取

獨占式:有且只有一個線程能獲取到鎖,如:ReentrantLock。</pre>

共享式:可以多個線程同時獲取到鎖,如:CountDownLatch

獨占式

  • 每個節點自旋觀察自己的前一節點是不是Header節點,如果是,就去嘗試獲取鎖。


    image.png
  • 獨占式鎖獲取流程:

image.png

共享式:

  • 共享式與獨占式的區別:


    image.png
  • 共享鎖獲取流程:
image.png

4、鎖的使用用例

4.1、ConcurrentHashMap的實現原理及使用

ConcurrentHashMap類圖
ConcurrentHashMap數據結構

結論:ConcurrentHashMap使用的鎖分段技術。首先將數據分成一段一段地存儲,然后給每一段數據配一把鎖,當一個線程占用鎖訪問其中一個段數據的時候,其他段的數據也能被其他線程訪問。

本文作者:景小財
作者簡介:美團外賣活動業務負責人


歡迎關注 高廣超的簡書博客 與 收藏文章 !

個人介紹:

高廣超:多年一線互聯網研發與架構設計經驗,擅長設計與落地高可用、高性能、可擴展的互聯網架構。

本文首發在 高廣超的簡書博客 轉載請注明!

簡書博客
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,119評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,382評論 3 415
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,038評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,853評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,616評論 6 408
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,112評論 1 323
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,192評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,355評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,869評論 1 334
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,727評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,928評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,467評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,165評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,570評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,813評論 1 282
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,585評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,892評論 2 372

推薦閱讀更多精彩內容