java并發編程(六)synchronized

設計同步器的意義
多線程編程中,有可能會出現多個線程同時訪問同一個共享、可變資源的情況,這個資源我們稱之其為臨界資源;這種資源可能是:對象、變量、文件等。
共享:資源可以由多個線程同時訪問
可變:資源可以在其生命周期內被修改

引出的問題:
由于線程執行的過程是不可控的,所以需要采用同步機制來協同對對象可變狀
態的訪問
那么我們怎么解決線程并發安全問題?
實際上,所有的并發模式在解決線程安全問題時,采用的方案都是 序列化訪問臨界資源。即在同一時刻,只能有一個線程訪問臨界資源,也稱作同步互斥訪問。
Java 中,提供了兩種方式來實現同步互斥訪問:synchronized 和 Lock
同步器的本質就是加鎖
加鎖目的序列化訪問臨界資源,即同一時刻只能有一個線程訪問臨界資源(同
步互斥訪問)不過有一點需要區別的是:當多個線程執行一個方法時,該方法內部的局部變量并不是臨界資源,因為這些局部變量是在每個線程的私有棧中,因此不具有共享性,不會導致線程安全問題。

synchronized原理詳解

synchronized內置鎖是一種對象鎖(鎖的是對象而非引用),作用粒度是對象,可以用來實現對臨界資源的同步互斥訪問,是可重入的。
加鎖的方式
同步實例方法,鎖是當前實例對象
同步類方法,鎖是當前類對象
同步代碼塊,鎖是括號里面的對象

synchronized底層原理

synchronized是基于JVM內置鎖實現,通過內部對象Monitor(監視器鎖)實現,基于進入與退出Monitor對象實現方法與代碼塊同步,監視器鎖的實現依賴底層操作系統的Mutex lock(互斥鎖)實現,它是一個重量級鎖性能較低。當然,JVM內置鎖在1.5之后版本做了重大的優化,如鎖粗化(Lock Coarsening)、鎖消除(Lock Elimination)、輕量級鎖(Lightweight Locking)、偏向鎖(Biased Locking)、適應性自旋(Adaptive Spinning)等技術來減少鎖操作的開銷,,內置鎖的并發性能已經基本與Lock持平。synchronized關鍵字被編譯成字節碼后會被翻譯成monitorentermonitorexit 兩條指令分別在同步塊邏輯代碼的起始位置與結束位置。

image.png

每個同步對象都有一個自己的Monitor(監視器鎖),加鎖過程如下圖所示:
image.png

那么有個問題來了,我們知道synchronized加鎖加在對象上,對象是如何記錄鎖狀態的呢?答案是鎖狀態是被記錄在每個對象的對象頭(Mark Word)中,下面我們一起認識一下對象的內存布局

對象的內存布局

HotSpot虛擬機中,對象在內存中存儲的布局可以分為三塊區域:對象頭(Header)、實例數據(Instance Data)和對齊填充(Padding)。
對象頭:比如 hash碼,對象所屬的年代,對象鎖,鎖狀態標志,偏向鎖(線程)ID,偏向時間,數組長度(數組對象)等
實例數據:即創建對象時,對象中成員變量,方法等
對齊填充:對象的大小必須是8字節的整數倍

image.png

對象頭
HotSpot虛擬機的對象頭包括兩部分信息,第一部分是Mark Word,用于存儲對象自身的運行時數據, 如哈希碼(HashCode)、GC分代年齡、鎖狀態標志、線程持有的鎖、偏向線程ID、偏向時間戳等等,這部分數據的長度在32位和64位的虛擬機(暫 不考慮開啟壓縮指針的場景)中分別為32個和64個Bits,官方稱它為“Mark Word”。對象需要存儲的運行時數據很多,其實已經超出了32、64位Bitmap結構所能記錄的限度,但是對象頭信息是與對象自身定義的數據無關的額 外存儲成本,考慮到虛擬機的空間效率,Mark Word被設計成一個非固定的數據結構以便在極小的空間內存儲盡量多的信息,它會根據對象的狀態復用自己的存儲空間。例如在32位的HotSpot虛擬機 中對象未被鎖定的狀態下,MarkWord的32個Bits空間中的25Bits用于存儲對象哈希碼(HashCode),4Bits用于存儲對象分代年齡,2Bits用于存儲鎖標志 位,1Bit固定為0,在其他狀態(輕量級鎖定、重量級鎖定、GC標記、可偏向)下對象的存儲內容如下表所示。

image.png

但是如果對象是數組類型,則需要三個機器碼,因為JVM虛擬機可以通過Java對象的元數據信息確定Java對象的大小,但是無法從數組的元數據來確認數組的大小,所以用一塊來記錄數組長度。對象頭信息是與對象自身定義的數據無關的額外存儲成本,但是考慮到虛擬機的空間效率,Mark Word被設計成一個非固定的數據結構以便在極小的空間內存存儲盡量多的數據,它會根據對象的狀態復用自己的存儲空間,也就是說,Mark Word會隨著程序的運行發生變化,變化狀態如下(32位虛擬機):
image.png

鎖的膨脹升級過程
鎖的狀態總共有四種,無鎖狀態、偏向鎖、輕量級鎖和重量級鎖。隨著鎖的
競爭,鎖可以從偏向鎖升級到輕量級鎖,再升級的重量級鎖,但是鎖的升級是單
向的,也就是說只能從低到高升級,不會出現鎖的降級。下圖為鎖的升級全過
程:

synchronized鎖實現與升級過程.png

偏向鎖
偏向鎖是Java 6之后加入的新鎖,它是一種針對加鎖操作的優化手段,經過研究發現,在大多數情況下,鎖不僅不存在多線程競爭,而且總是由同一線程多次獲得,因此為了減少同一線程獲取鎖(會涉及到一些CAS操作,耗時)的代價而引入偏向鎖。偏向鎖的核心思想是,如果一個線程獲得了鎖,那么鎖就進入偏向模式,此時Mark Word 的結構也變為偏向鎖結構,當這個線程再次請求鎖時,無需再做任何同步操作,即獲取鎖的過程,這樣就省去了大量有關鎖申請的操作,從而也就提供程序的性能。所以,對于沒有鎖競爭的場合,偏向鎖有很好的優化效果,畢竟極有可能連續多次是同一個線程申請相同的鎖。但是對于鎖競爭比較激烈的場合,偏向鎖就失效了,因為這樣場合極有可能每次申請鎖的線程都是不相同的,因此這種場合下不應該使用偏向鎖,否則會得不償失,需要注意的是,偏向鎖失敗后,并不會立即膨脹為重量級鎖,而是先升級為輕量級鎖。下面我們接著了解輕量級鎖。

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

自旋鎖
輕量級鎖失敗后,虛擬機為了避免線程真實地在操作系統層面掛起,還會進
行一項稱為自旋鎖的優化手段。這是基于在大多數情況下,線程持有鎖的時間都不會太長,如果直接掛起操作系統層面的線程可能會得不償失,畢竟操作系統實現線程之間的切換時需要從用戶態轉換到核心態,這個狀態之間的轉換需要相對比較長的時間,時間成本相對較高,因此自旋鎖會假設在不久將來,當前的線程可以獲得鎖,因此虛擬機會讓當前想要獲取鎖的線程做幾個空循環(這也是稱為自旋的原因),一般不會太久,可能是50個循環或100循環,在經過若干次循環后,如果得到鎖,就順利進入臨界區。如果還不能獲得鎖,那就會將線程在操作系統層面掛起,這就是自旋鎖的優化方式,這種方式確實也是可以提升效率的。最后沒辦法也就只能升級為重量級鎖了。

鎖消除
消除鎖是虛擬機另外一種鎖的優化,這種優化更徹底,Java虛擬機在JIT編
譯時(可以簡單理解為當某段代碼即將第一次被執行時進行編譯,又稱即時編
譯),通過對運行上下文的掃描,去除不可能存在共享資源競爭的鎖,通過這種
方式消除沒有必要的鎖,可以節省毫無意義的請求鎖時間,如下StringBuffer的
append是一個同步方法,但是在add方法中的StringBuffer屬于一個局部變量,
并且不會被其他線程所使用,因此StringBuffer不可能存在共享資源競爭的情
景,JVM會自動將其鎖消除。

逃逸分析
使用逃逸分析,編譯器可以對代碼做如下優化:
一、同步省略。如果一個對象被發現只能從一個線程被訪問到,那么對于這個對象的操作可以不考慮同步。
二、將堆分配轉化為棧分配。如果一個對象在子程序中被分配,要使指向該對象的指針永遠不會逃逸,對象可能是棧分配的候選,而不是堆分配。
三、分離對象或標量替換。有的對象可能不需要作為一個連續的內存結構存在也可以被訪問到,那么對象的部分(或全部)可以不存儲在內存,而是存儲在CPU寄存器中。

是不是所有的對象和數組都會在堆內存分配空間?

不一定
在Java代碼運行時,通過JVM參數可指定是否開啟逃逸分析, XX:+
DoEscapeAnalysis : 表示開啟逃逸分析 XX:DoEscapeAnalysis: 表示關
閉逃逸分析 從jdk 1.7開始已經默認開始逃逸分析,如需關閉,需要指定XX:
DoEscapeAnalysis
/**

  • 進行兩種測試
  • 關閉逃逸分析,同時調大堆空間,避免堆內GC的發生,如果有GC信息將會被打印
    出來
  • VM運行參數:Xmx4G
    Xms4G
    XX:
    DoEscapeAnalysis
    XX:+
    PrintGCDetails XX:+
    HeapDumpOnOutOfMemoryError
  • 開啟逃逸分析
  • VM運行參數:Xmx4G
    Xms4G
    XX:+
    DoEscapeAnalysis XX:+
    PrintGCDetails XX:+
    HeapDumpOnOutOfMemoryError
  • 執行main方法后
  • jps 查看進程
  • jmap histo
    進程ID

*/

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

推薦閱讀更多精彩內容