Java 深入分析 - 并發(fā) Synchronized

Synchronized

synchronized 可以用來修飾

  • 方法
  • 靜態(tài)方法
  • 對(duì)象

但其實(shí)本質(zhì)就是 鎖對(duì)象脉课,修飾方法時(shí)鎖 this,修飾靜態(tài)方法時(shí)鎖 class财异,當(dāng)一個(gè)線程獲取了一個(gè)對(duì)象的鎖倘零,那么后到的線程都需要等待鎖被釋放

    void synchronized syn() {...}

如上代碼其實(shí)等同于如下代碼

    void syn() {
        synchronized(this) {
            // ...
        }
    }

對(duì)象鎖的狀態(tài)大致分為四種,鎖隨著競(jìng)爭(zhēng)激烈應(yīng)當(dāng)逐步升級(jí)戳寸,但不允許回退

  • 無鎖
  • 偏向鎖
  • 輕量級(jí)鎖
  • 重量級(jí)鎖

我們開始重點(diǎn)討論 synchronized 中鎖的實(shí)現(xiàn)以及加鎖策略之前呈驶,需要明白以下概念,在 Java 對(duì)象頭里的有一個(gè)字段叫 Mark Word疫鹊,其中存儲(chǔ)了對(duì)象的 HashCode袖瞻,分代年齡和 鎖信息,而鎖信息就代表著對(duì)象加的是一把什么鎖

互斥鎖

每個(gè)對(duì)象擁有一個(gè)屬于自己的寶貝 - monitor拆吆,它就是一把 互斥鎖聋迎,占有鎖時(shí) 排斥任何人

monitor 十分重量級(jí),每個(gè)對(duì)象誕生時(shí)枣耀,monitor 會(huì)被初始化為 0

monitor 是一種可重入的砌庄,重量級(jí)的互斥鎖,之所以稱之為 重量級(jí)奕枢,因?yàn)槠浠诒O(jiān)視器 monitor娄昆,而 monitor 本質(zhì)又依賴于底層操作系統(tǒng) Mutex Lock,線程的阻塞和喚醒需要CPU從用戶態(tài)轉(zhuǎn)為核心態(tài)缝彬,頻繁的阻塞和喚醒對(duì)CPU來說是一件負(fù)擔(dān)很重的工作,線程之間的切換成本非常高萌焰,應(yīng)此, monitor 應(yīng)當(dāng)成為同步的最后一種手段

獲取與釋放

對(duì)應(yīng)的虛擬機(jī)指令分別為 monitorentermonitorexit

  • monitorenter : 嘗試獲取該對(duì)象的鎖
  • monitorexit : 釋放該對(duì)象鎖

如果我們有一個(gè)線程試圖占用對(duì)象谷浅,占用后進(jìn)行一定操作 some process扒俯,之后便結(jié)束占用,那么虛擬機(jī)指令是如此執(zhí)行的

    // before monitorenter ...
    monitorenter
    // try own lock of object successfully and do something ...
    monitorexit
    // after monitorexit ...

線程嘗試獲取一把互斥鎖時(shí)一疯,需要經(jīng)過以下步驟

  • monitor = 0 撼玄,線程獲得一把互斥鎖,并且 monitor++
  • 若 線程已擁有互斥鎖墩邀,仍然 monitor++
  • monitor != 0 并且 線程未擁有鎖掌猛,掛起線程,等待鎖被釋放

線程釋放一把互斥鎖時(shí)眉睹,需要經(jīng)過以下步驟

  • monitorexitmonitor--
  • monitor = 0 后荔茬,喚醒等待線程废膘,通知鎖已經(jīng)被釋放

自適應(yīng)自旋鎖

線程的阻塞、掛起和喚醒非常損耗性能慕蔚,耗時(shí)比同步代碼更長(zhǎng)的情況很可能出現(xiàn)丐黄,如果同步代碼執(zhí)行本來就比較短,那為何要進(jìn)行線程切換呢

自旋鎖就是為解決這個(gè)問題而出現(xiàn)的孔飒,當(dāng)線程嘗試獲取鎖失敗時(shí)灌闺,線程會(huì)自旋一段時(shí)間后,再嘗試獲取鎖坏瞄,如果失敗那再掛起

自旋過程中桂对,可以理解為等待鎖被釋放,自旋意思就是做其他事情等待鎖被釋放惦积,你可以坐著,躺著或干啥都行

自旋性能的關(guān)鍵在于 自旋時(shí)長(zhǎng) 如何決定猛频,JVM 中采用的是一種 自適應(yīng) 策略狮崩,即通過觀察以往自旋時(shí)間,若通過該自旋時(shí)間后線程獲取鎖成功鹿寻,那么下一次自旋這么久也很有可能成功睦柴,反著則反,若一直失敗毡熏,則直接取消自旋會(huì)更加劃算

鎖撤銷

當(dāng)虛擬機(jī)檢測(cè)到坦敌,同步的地方并沒有同步價(jià)值時(shí),編譯時(shí)會(huì)撤銷該鎖痢法,如下面代碼塊狱窘,則完全不需要同步,String 是不可變對(duì)象财搁,完全不可能被修改

    public synchronized void print(String s) {
        System.out.println(s);
    }

鎖粗化

我們平時(shí)寫代碼一般要求將鎖細(xì)化蘸炸,即只講鎖加在需要同步的地方,然后鎖的獲取與釋放尖奔,像剛才提到的互斥鎖搭儒,是非常耗性能的,因此 當(dāng)可優(yōu)化的多段鎖出現(xiàn)時(shí)提茁,會(huì)粗化該鎖

    public String plus(String s1, String s2, String s3) {
        return s1 + s2 + s3;
    }

如上述語句可能并發(fā)現(xiàn)不了什么不得了的事情淹禾,但字符串的拼接是挺費(fèi)性能的,應(yīng)當(dāng)使用 StringBuilder 或者 StringBuffer茴扁,虛擬機(jī)則如下優(yōu)化

    public String plus(String s1, String s2, String s3) {
        StringBuffer buffer = new StringBuffer();
        buffer.append(s1);
        buffer.append(s2);
        buffer.append(s3);
        return buffer.toString();
    }

StringBuffer 是同步的铃岔,所以拼接時(shí)會(huì)上三把鎖,然而一把鎖的性能會(huì)更好

輕量級(jí)鎖

使用重量級(jí)互斥鎖時(shí)峭火,我們應(yīng)當(dāng)考慮一個(gè)問題德撬,一把互斥鎖 并不是每時(shí)都在被多線程競(jìng)爭(zhēng)铲咨,在不被競(jìng)爭(zhēng)時(shí),排斥鎖的線程切換蜓洪,就顯得沒那么必要了纤勒,這就是一個(gè)優(yōu)化的點(diǎn),所以就引入了輕量級(jí)鎖

在決定使用底層操作系統(tǒng)的 monitor 之前隆檀,應(yīng)當(dāng)先考慮 輕量級(jí)鎖摇天,它不使用線程切換,但能達(dá)到同步目的

獲取與釋放

線程嘗試獲取一把輕量級(jí)鎖時(shí)恐仑,需要經(jīng)過以下步驟

  • Mark Word 無鎖泉坐,則為當(dāng)前棧幀開辟出一塊 Lock Record 內(nèi)存,并拷貝一份對(duì)象 Mark Word 信息
  • 嘗試將 Mark Word 指向 Lock RecordCAS
    • 若成功裳仆,線程獲得一把輕量級(jí)鎖腕让,并將 Lock Record 指向 Mark Word
    • 若失敗
      • Mark Word 指向 Lock Record,說明已經(jīng)擁有鎖
      • 若 未指向歧斟,說明存在多線程競(jìng)爭(zhēng)纯丸,并將 Mark Word 的鎖狀態(tài) 由輕量級(jí)鎖膨脹為重量級(jí)鎖

線程釋放一把輕量級(jí)鎖時(shí),需要經(jīng)過以下步驟

  • 嘗試將 Lock Record 替換 Mark WordCAS
    • 若成功静袖,正常釋放
    • 若失敗觉鼻,說明在擁有鎖的過程中,鎖已經(jīng)膨脹為重量級(jí)鎖队橙,則喚醒等待線程坠陈,通知鎖已經(jīng)被釋放

在無競(jìng)爭(zhēng)條件下,輕量級(jí)鎖無需進(jìn)行線程切換捐康,只需要鎖獲取和釋放時(shí)的兩次 CAS 操作

偏向鎖

使用輕量級(jí)鎖時(shí)仇矾,我們應(yīng)當(dāng)考慮一個(gè)問題,CAS 是很消耗 CPU 的解总,一個(gè)對(duì)象只被一個(gè)線程訪問時(shí)若未,不存在輕量級(jí)鎖競(jìng)爭(zhēng),那么輕量級(jí)鎖的兩次 CAS 操作和內(nèi)存拷貝倾鲫,就顯得些許多余粗合,所以就引入了偏向鎖

偏向鎖是指對(duì)象偏向某個(gè)線程,即在無線程競(jìng)爭(zhēng)條件下乌昔,該對(duì)象是屬于某個(gè)線程的隙疚,一旦出現(xiàn)競(jìng)爭(zhēng),應(yīng)當(dāng)撤銷偏向鎖 并升級(jí)為輕量級(jí)鎖

獲取與撤銷

線程嘗試獲取一把偏向鎖時(shí)磕道,需要經(jīng)過以下步驟

  • Mark Word 無鎖供屉,嘗試將 currentThreadId 復(fù)制到 Lock RecordMark Word 中 (CAS
    • 若成功,線程獲得一把偏向鎖
    • 若失敗,說明存在其他線程競(jìng)爭(zhēng)偏向鎖伶丐,并撤銷偏向鎖

撤銷偏向鎖時(shí)悼做,需等到安全點(diǎn) SafePoint 再執(zhí)行撤銷

加鎖策略

虛擬機(jī)的加鎖策略,是一層一層升級(jí)的哗魂,簡(jiǎn)而言之可以如下總結(jié)

  • 當(dāng)競(jìng)爭(zhēng)對(duì)象無鎖時(shí)肛走,先嘗試偏向鎖
  • 當(dāng)存在不止一個(gè)線程獲取偏向鎖時(shí),應(yīng)當(dāng)嘗試輕量級(jí)鎖
  • 當(dāng)存在多線程競(jìng)爭(zhēng)一個(gè)輕量級(jí)鎖時(shí)录别,需考慮重量級(jí)鎖
  • 當(dāng)嘗試競(jìng)爭(zhēng)重量級(jí)鎖失敗時(shí)朽色,應(yīng)先嘗試自旋鎖再考慮線程切換策略

狀態(tài)轉(zhuǎn)換

當(dāng)線程競(jìng)爭(zhēng)鎖時(shí),它們之間的狀態(tài)轉(zhuǎn)化如上圖

  • Contention List:競(jìng)爭(zhēng)隊(duì)列
  • Entry List:有資格成為候選人的競(jìng)爭(zhēng)隊(duì)列
  • OnDeck:鎖候選人
  • Owner:鎖持有者
  • Wait Set:線程等待池

synchronized 配套使用的 wait / notify / notifyAll组题,下面我們來探討它們是如何實(shí)現(xiàn)的

  • wait : 釋放鎖葫男,進(jìn)入等待池
  • notify : 從等待池中挑選一名同學(xué)再次參與競(jìng)爭(zhēng)
  • notifyAll : 等待池全部恢復(fù)競(jìng)爭(zhēng)

參考

  1. 《深入理解Java虛擬機(jī)》
  2. 《Java并發(fā)編程實(shí)戰(zhàn)》
  3. 《Java并發(fā)編程藝術(shù)》
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市崔列,隨后出現(xiàn)的幾起案子梢褐,更是在濱河造成了極大的恐慌,老刑警劉巖赵讯,帶你破解...
    沈念sama閱讀 216,692評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件盈咳,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡瘦癌,警方通過查閱死者的電腦和手機(jī)猪贪,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,482評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門跷敬,熙熙樓的掌柜王于貴愁眉苦臉地迎上來讯私,“玉大人,你說我怎么就攤上這事西傀〗锟埽” “怎么了?”我有些...
    開封第一講書人閱讀 162,995評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵拥褂,是天一觀的道長(zhǎng)娘锁。 經(jīng)常有香客問我,道長(zhǎng)饺鹃,這世上最難降的妖魔是什么莫秆? 我笑而不...
    開封第一講書人閱讀 58,223評(píng)論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮悔详,結(jié)果婚禮上镊屎,老公的妹妹穿的比我還像新娘。我一直安慰自己茄螃,他們只是感情好缝驳,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,245評(píng)論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般用狱。 火紅的嫁衣襯著肌膚如雪运怖。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,208評(píng)論 1 299
  • 那天夏伊,我揣著相機(jī)與錄音摇展,去河邊找鬼。 笑死署海,一個(gè)胖子當(dāng)著我的面吹牛吗购,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播砸狞,決...
    沈念sama閱讀 40,091評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼捻勉,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了刀森?” 一聲冷哼從身側(cè)響起踱启,我...
    開封第一講書人閱讀 38,929評(píng)論 0 274
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎研底,沒想到半個(gè)月后埠偿,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,346評(píng)論 1 311
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡榜晦,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,570評(píng)論 2 333
  • 正文 我和宋清朗相戀三年冠蒋,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片乾胶。...
    茶點(diǎn)故事閱讀 39,739評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡抖剿,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出识窿,到底是詐尸還是另有隱情斩郎,我是刑警寧澤,帶...
    沈念sama閱讀 35,437評(píng)論 5 344
  • 正文 年R本政府宣布喻频,位于F島的核電站缩宜,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏甥温。R本人自食惡果不足惜锻煌,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,037評(píng)論 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望姻蚓。 院中可真熱鬧宋梧,春花似錦、人聲如沸史简。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,677評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至跺讯,卻和暖如春枢贿,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背刀脏。 一陣腳步聲響...
    開封第一講書人閱讀 32,833評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工局荚, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人愈污。 一個(gè)月前我還...
    沈念sama閱讀 47,760評(píng)論 2 369
  • 正文 我出身青樓耀态,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親暂雹。 傳聞我的和親對(duì)象是個(gè)殘疾皇子首装,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,647評(píng)論 2 354

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