1.Synchronized原理

在多線程并發(fā)編程中 synchronized 一直是元老級角色,很多人都會稱呼它為重量級鎖收苏。但是漓柑,隨著 Java SE 1.6 對synchronized 進(jìn)行了各種優(yōu)化之后驰吓,有些情況下它就并不那么重凳兵,Java SE 1.6 中為了減少獲得鎖和釋放鎖帶來的性能消耗而引入的偏向鎖和輕量級鎖

鎖的存儲

JVM 源碼實現(xiàn)

當(dāng)我們在 Java 代碼中飞蹂,使用 new 創(chuàng)建一個對象實例的時候薇缅,(hotspot 虛擬機)JVM 層面實際上會創(chuàng)建一個instanceOopDesc 對象。
Hotspot 虛擬機采用 OOP-Klass 模型來描述 Java 對象實例饭望,OOP(Ordinary Object Point)指的是普通對象指針塑娇,Klass 用來描述對象實例的具體類型芥永。Hotspot 采用instanceOopDesc 和 arrayOopDesc 來 描述對象 頭,arrayOopDesc 對象用來描述數(shù)組類型instanceOopDesc 的定義在 Hotspot 源 碼 中 的instanceOop.hpp 文件中钝吮,另外,arrayOopDesc 的定義對應(yīng) arrayOop.hpp


image.png

從 instanceOopDesc 代碼中可以看到 instanceOopDesc繼承自 oopDesc,oopDesc 的定義載 Hotspot 源碼中的oop.hpp 文件中,在普通實例對象中奇瘦,oopDesc 的定義包含兩個成員棘催,分別是 _mark 和 _metadata_mark 表示對象標(biāo)記、屬于 markOop 類型耳标,也就是Mark World醇坝,它記錄了對象和鎖有關(guān)的信息_metadata 表示類元信息,類元信息存儲的是對象指向它的類元數(shù)據(jù)(Klass)的首地址次坡,其中 Klass 表示普通指針呼猪、_compressed_klass 表示壓縮類指針

Mark World

在 Hotspot 中,markOop 的定義在 markOop.hpp 中


image.png

Mark word 記錄了對象和鎖有關(guān)的信息砸琅,當(dāng)某個對象被synchronized 關(guān)鍵字當(dāng)成同步鎖時宋距,那么圍繞這個鎖的一系列操作都和 Mark word 有關(guān)系。Mark Word 在 32 位虛擬機的長度是 32bit症脂、在 64 位虛擬機的長度是 64bit谚赎,Mark Word 里面存儲的數(shù)據(jù)會隨著鎖標(biāo)志位的變化而變化。

Java 對象頭

鎖存在 Java 對象頭里诱篷。如果對象是數(shù)組類型壶唤,則虛擬機用 3 個 Word(字寬)存儲對象頭,如果對象是非數(shù)組類型棕所,則用 2 字寬存儲對象頭闸盔。在 32 位虛擬機中,一字寬等于四字節(jié)琳省,即 32bit迎吵。


image.png

Java 對象頭里的 Mark Word 里默認(rèn)存儲對象的 HashCode,分代年齡和鎖標(biāo)記位岛啸。32 位 JVM 的 Mark Word 的默認(rèn)存儲結(jié)構(gòu)如下:


image.png

在運行期間 Mark Word 里存儲的數(shù)據(jù)會隨著鎖標(biāo)志位的變化而變化钓觉。Mark Word 可能變化為存儲以下 4 種數(shù)據(jù):


image.png

在 64 位虛擬機下,Mark Word 是 64bit 大小的坚踩,其存儲結(jié)構(gòu)如下:
image.png

為什么任何對象都可以實現(xiàn)鎖

  1. 首先荡灾,Java 中的每個對象都派生自 Object 類,而每個Java Object 在 JVM 內(nèi)部都有一個 native 的 C++對象oop/oopDesc 進(jìn)行對應(yīng)瞬铸。
  2. 線程在獲取鎖的時候批幌,實際上就是獲得一個監(jiān)視器對象(monitor) ,monitor 可以認(rèn)為是一個同步對象,所有的Java 對象是天生攜帶 monitor嗓节。在 hotspot 源碼的markOop.hpp 文件中荧缘,可以看到下面這段代碼。


    image.png

    多個線程訪問同步代碼塊時拦宣,相當(dāng)于去爭搶對象監(jiān)視器修改對象中的鎖標(biāo)識,上面的代碼中ObjectMonitor這個對象和線程爭搶鎖的邏輯有密切的關(guān)系

鎖升級

Java SE1.6 為了減少獲得鎖和釋放鎖所帶來的性能消耗截粗,引入了“偏向鎖”和“輕量級鎖”信姓,所以在 Java SE1.6 里鎖一共有四種狀態(tài),無鎖狀態(tài)绸罗,偏向鎖狀態(tài)意推,輕量級鎖狀態(tài)和重量級鎖狀態(tài),它會隨著競爭情況逐漸升級珊蟀。鎖可以升級但不能降級菊值,意味著偏向鎖升級成輕量級鎖后不能降級成偏向鎖。這種鎖升級卻不能降級的策略育灸,目的是為了提高獲得鎖和釋放鎖的效率腻窒。


image.png

偏向鎖

當(dāng)一個線程訪問加了同步鎖的代碼塊時,會在對象頭中存儲當(dāng)前線程的 ID磅崭,后續(xù)這個線程進(jìn)入和退出這段加了同步鎖的代碼塊時儿子,不需要再次加鎖和釋放鎖。而是直接比較對象頭里面是否存儲了指向當(dāng)前線程的偏向鎖绽诚。如果相等表示偏向鎖是偏向于當(dāng)前線程的典徊,就不需要再嘗試獲得鎖了

  • 獲取偏向鎖

    1. 首先獲取鎖 對象的 Markword,判斷是否處于可偏向狀態(tài)恩够。(biased_lock=1卒落、且 ThreadId 為空)
    2. 如果是可偏向狀態(tài),則通過 CAS 操作蜂桶,把當(dāng)前線程的 ID寫入到 MarkWord
      a) 如果 cas 成功儡毕,那么 markword 就會變成這樣。表示已經(jīng)獲得了鎖對象的偏向鎖扑媚,接著執(zhí) 行同步代碼塊
      b) 如果 cas 失敗腰湾,說明有其他線程已經(jīng)獲得了偏向鎖,這種情況說明當(dāng)前鎖存在競爭疆股,需要撤銷已獲得偏向鎖的線程费坊,并且把它持有的鎖升級為輕量級鎖(這個操作需要等到全局安全點,也就是沒有線程在執(zhí)行字節(jié)碼)才能執(zhí)行
    3. 如果是已偏向狀態(tài)旬痹,需要檢查 markword 中存儲的ThreadID 是否等于當(dāng)前線程的 ThreadID
      a) 如果相等附井,不需要再次獲得鎖,可直接執(zhí)行同步代碼塊
      b) 如果不相等两残,說明當(dāng)前鎖偏向于其他線程永毅,需要撤銷偏向鎖并升級到輕量級鎖
  • 撤銷偏向鎖
    偏向鎖的撤銷并不是把對象恢復(fù)到無鎖可偏向狀態(tài)(因為偏向鎖并不存在鎖釋放的概念),而是在獲取偏向鎖的過程中人弓,發(fā)現(xiàn) cas 失敗也就是存在線程競爭時沼死,直接把被偏向的鎖對象升級到被加了輕量級鎖的狀態(tài)。

    對原持有偏向鎖的線程進(jìn)行撤銷時崔赌,原獲得偏向鎖的線程有兩種情況:

    1. 原獲得偏向鎖的線程如果已經(jīng)退出了臨界區(qū)意蛀,也就是同步代碼塊執(zhí)行完了耸别,那么這個時候會把對象頭設(shè)置成無鎖狀態(tài)并且爭搶鎖的線程可以基于 CAS 重新偏向當(dāng)前線程。
    2. 如果原獲得偏向鎖的線程的同步代碼塊還沒執(zhí)行完浸间,處于臨界區(qū)之內(nèi)太雨,這個時候會把原獲得偏向鎖的線程升級為輕量級鎖后繼續(xù)執(zhí)行同步代碼塊。

在我們的應(yīng)用開發(fā)中魁蒜,絕大部分情況下一定會存在 2 個以上的線程競爭,那么如果開啟偏向鎖吩翻,反而會提升獲取鎖的資源消耗兜看。所以可以通過 jvm 參數(shù)UseBiasedLocking 來設(shè)置開啟或關(guān)閉偏向鎖。

偏向鎖的流程圖分析

偏向鎖.png

輕量級鎖

  • 輕量級鎖的加鎖
    鎖升級為輕量級鎖之后狭瞎,對象的 Markword 也會進(jìn)行相應(yīng)的的變化细移。升級為輕量級鎖的過程:
    1. 線程在自己的棧楨中創(chuàng)建鎖記錄 LockRecord。
    2. 將鎖對象的對象頭中的MarkWord復(fù)制到線程的剛剛創(chuàng)建的鎖記錄中熊锭。
    3. 將鎖記錄中的 Owner 指針指向鎖對象弧轧。
    4. 將鎖對象的對象頭的 MarkWord替換為指向鎖記錄的指針。


      image.png

      image.png
  • 自旋鎖
    輕量級鎖在加鎖過程中碗殷,用到了自旋鎖
    所謂自旋精绎,就是指當(dāng)有另外一個線程來競爭鎖時,這個線程會在原地循環(huán)等待锌妻,而不是把該線程給阻塞代乃,直到那個獲得鎖的線程釋放鎖之后,這個線程就可以馬上獲得鎖的仿粹。注意搁吓,鎖在原地循環(huán)的時候,是會消耗 cpu 的吭历,就相當(dāng)于在執(zhí)行一個啥也沒有的 for 循環(huán)堕仔。所以,輕量級鎖適用于那些同步代碼塊執(zhí)行的很快的場景晌区,這樣摩骨,線程原地等待很短的時間就能夠獲得鎖了。

自旋鎖的使用契讲,其實也是有一定的概率背景仿吞,在大部分同步代碼塊執(zhí)行的時間都是很短的。所以通過看似無異議的循環(huán)反而能提升鎖的性能捡偏。但是自旋必須要有一定的條件控制唤冈,否則如果一個線程執(zhí)行同步代碼塊的時間很長,那么這個線程不斷的循環(huán)反而會消耗 CPU 資源银伟。默認(rèn)情況下自旋的次數(shù)是 10 次你虹,可以通過 preBlockSpin 來修改绘搞。
在 JDK1.6 之后,引入了自適應(yīng)自旋鎖傅物,自適應(yīng)意味著自旋的次數(shù)不是固定不變的夯辖,而是根據(jù)前一次在同一個鎖上自旋的時間以及鎖的擁有者的狀態(tài)來決定。
如果在同一個鎖對象上董饰,自旋等待剛剛成功獲得過鎖蒿褂,并且持有鎖的線程正在運行中,那么虛擬機就會認(rèn)為這次自旋也是很有可能再次成功卒暂,進(jìn)而它將允許自旋等待持續(xù)相對更長的時間啄栓。如果對于某個鎖,自旋很少成功獲得過也祠,那在以后嘗試獲取這個鎖時將可能省略掉自旋過程昙楚,直接阻塞線程,避免浪費處理器資源

  • 輕量級鎖的解鎖
    輕量級鎖的鎖釋放邏輯其實就是獲得鎖的逆向邏輯诈嘿,通過CAS 操作把線程棧幀中的 LockRecord 替換回到鎖對象的MarkWord 中堪旧,如果成功表示沒有競爭。如果失敗奖亚,表示當(dāng)前鎖存在競爭淳梦,那么輕量級鎖就會膨脹成為重量級鎖

輕量級鎖的流程圖分析

輕量級鎖.png

重量級鎖

當(dāng)輕量級鎖膨脹到重量級鎖之后,意味著線程只能被掛起阻塞來等待被喚醒了遂蛀。
重量鎖在JVM中又叫對象監(jiān)視器(Monitor)谭跨,它很像C中的Mutex,除了具備Mutex(0|1)互斥的功能李滴,它還負(fù)責(zé)實現(xiàn)了Semaphore(信號量)的功能螃宙,也就是說它至少包含一個競爭鎖的隊列,和一個信號阻塞隊列(wait隊列)所坯,前者負(fù)責(zé)做互斥谆扎,后一個用于做線程同步.


image.png

加了同步代碼塊以后,在字節(jié)碼中會看到一個monitorenter 和 monitorexit芹助。
每一個 JAVA 對象都會與一個監(jiān)視器 monitor 關(guān)聯(lián)堂湖,我們可以把它理解成為一把鎖,當(dāng)一個線程想要執(zhí)行一段被synchronized 修飾的同步方法或者代碼塊時状土,該線程得先獲取到 synchronized 修飾的對象對應(yīng)的 monitor无蜂。
monitorenter 表示去獲得一個對象監(jiān)視器。monitorexit 表示釋放 monitor 監(jiān)視器的所有權(quán)蒙谓,使得其他被阻塞的線程可以嘗試去獲得這個監(jiān)視器
monitor 依賴操作系統(tǒng)的 MutexLock(互斥鎖)來實現(xiàn)的, 線程被阻塞后便進(jìn)入內(nèi)核(Linux)調(diào)度狀態(tài)斥季,這個會導(dǎo)致系統(tǒng)在用戶態(tài)與內(nèi)核態(tài)之間來回切換,嚴(yán)重影響鎖的性能

image.png

——學(xué)自咕泡學(xué)院

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市酣倾,隨后出現(xiàn)的幾起案子舵揭,更是在濱河造成了極大的恐慌,老刑警劉巖躁锡,帶你破解...
    沈念sama閱讀 218,525評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件午绳,死亡現(xiàn)場離奇詭異,居然都是意外死亡映之,警方通過查閱死者的電腦和手機拦焚,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,203評論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來杠输,“玉大人耕漱,你說我怎么就攤上這事√牛” “怎么了?”我有些...
    開封第一講書人閱讀 164,862評論 0 354
  • 文/不壞的土叔 我叫張陵灾梦,是天一觀的道長峡钓。 經(jīng)常有香客問我,道長若河,這世上最難降的妖魔是什么能岩? 我笑而不...
    開封第一講書人閱讀 58,728評論 1 294
  • 正文 為了忘掉前任,我火速辦了婚禮萧福,結(jié)果婚禮上拉鹃,老公的妹妹穿的比我還像新娘。我一直安慰自己鲫忍,他們只是感情好膏燕,可當(dāng)我...
    茶點故事閱讀 67,743評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著悟民,像睡著了一般坝辫。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上射亏,一...
    開封第一講書人閱讀 51,590評論 1 305
  • 那天近忙,我揣著相機與錄音,去河邊找鬼智润。 笑死及舍,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的窟绷。 我是一名探鬼主播锯玛,決...
    沈念sama閱讀 40,330評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼钾麸!你這毒婦竟也來了更振?” 一聲冷哼從身側(cè)響起炕桨,我...
    開封第一講書人閱讀 39,244評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎肯腕,沒想到半個月后献宫,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,693評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡实撒,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,885評論 3 336
  • 正文 我和宋清朗相戀三年姊途,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片知态。...
    茶點故事閱讀 40,001評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡捷兰,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出负敏,到底是詐尸還是另有隱情贡茅,我是刑警寧澤,帶...
    沈念sama閱讀 35,723評論 5 346
  • 正文 年R本政府宣布其做,位于F島的核電站顶考,受9級特大地震影響尘惧,放射性物質(zhì)發(fā)生泄漏舷蒲。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,343評論 3 330
  • 文/蒙蒙 一况褪、第九天 我趴在偏房一處隱蔽的房頂上張望蹈胡。 院中可真熱鬧渊季,春花似錦、人聲如沸罚渐。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,919評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽搅轿。三九已至病涨,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間璧坟,已是汗流浹背既穆。 一陣腳步聲響...
    開封第一講書人閱讀 33,042評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留雀鹃,地道東北人幻工。 一個月前我還...
    沈念sama閱讀 48,191評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像黎茎,于是被迫代替她去往敵國和親囊颅。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,955評論 2 355

推薦閱讀更多精彩內(nèi)容