synchronized詳解
解釋
synchronized是jvm級別的一種重量級鎖们何,但是隨著jdk對synchronized的不斷優(yōu)化,現(xiàn)在它已經(jīng)變得沒有我們想象的那么重了。由于synchronized使用簡單根资,也不用手動釋放鎖,因此我們平時開發(fā)中用到最多的鎖就是它了。
synchronized鎖的三種形式
- 普通方法:鎖是當前對象實例
- 靜態(tài)方法:鎖是當前類的Class對象
- 同步方法塊:鎖是synchronized括號里的對象
實現(xiàn)原理
同步代碼塊在編譯后會在前后分別插入monitorenter和monitorexit指令热幔,每個對象在同一時刻只會與一個monitor相關(guān)聯(lián),當線程執(zhí)行到monitorenter指令時就會嘗試獲取對象所對應(yīng)的monitor的所有權(quán)讼庇,如果這個monitor已經(jīng)被其他線程獲取绎巨,則需要等待鎖釋放。
對象頭
synchronized鎖是存在對象頭中的蠕啄。如果對象是數(shù)組類型场勤,則虛擬機用3個字寬存儲對象頭,如果對象是非數(shù)組類型歼跟,則用2個字寬存儲對象頭和媳。在32位虛擬機中,1字寬等于4字節(jié)哈街,即32bit留瞳。
補充一點:Java對象保存在內(nèi)存中,由三部分組成:對象頭骚秦、實例數(shù)據(jù)她倘、對齊填充字節(jié)。Java頭由三部分組成:Mark Word作箍、指向類的指針帝牡、數(shù)組長度(只有數(shù)組對象才有)。
32位jvm的Mark Word的存儲結(jié)構(gòu)如下
鎖狀態(tài) | 25bit | 4bit | 1bit是否偏向鎖 | 2bit鎖標志位 |
---|---|---|---|---|
無鎖狀態(tài) | 對象的hashCode | 對象分代年齡 | 0 | 01 |
Mark Word中的數(shù)據(jù)隨著鎖標志位的變化而變化蒙揣,如下
鎖的升級
java1.6以后靶溜,為了減少獲取鎖和釋放鎖的性能消耗,引入了“偏向鎖”和”輕量級鎖“懒震。鎖的狀態(tài)可以從無鎖狀態(tài)->偏向鎖->輕量級鎖->重量級鎖罩息,隨著競爭情況逐漸升級,但是不能降級个扰。
偏向鎖
大多數(shù)情況下瓷炮,鎖不僅不存在多線程競爭,而且總是由同一個線程多次獲得递宅,為了讓線程獲得鎖的代價更低引入了偏向鎖娘香。當一個線程訪問同步塊并獲取鎖時苍狰,會在對象頭和棧幀中的鎖記錄里存儲鎖偏向的線程ID,以后該線程在進入和退出同步塊時不需要進行CAS操作來加鎖和解釋烘绽,只需要簡單地測試一下對象頭的Mark Word里是否存儲著指向當前線程的偏向鎖淋昭。如果是,則直接獲得鎖安接,執(zhí)行同步塊翔忽;如果不是,則使用CAS操作更改線程ID盏檐,更改成功獲得鎖歇式,更改失敗開始撤銷偏向鎖。
撤銷偏向鎖
偏向鎖只有存在鎖競爭的情況下才會釋放胡野。撤銷偏向鎖需要等待全局安全點(在這個時間點上沒有正在執(zhí)行的字節(jié)碼)材失,首先暫停偏向鎖持有的線程,然后檢查此線程是否活著硫豆,如果線程不處于活動狀態(tài)豺憔,則轉(zhuǎn)成無鎖狀態(tài);如果還活著够庙,升級為輕量級鎖恭应。下圖展示了偏向鎖的獲得與撤銷過程
輕量級鎖
- 加鎖:線程在執(zhí)行同步塊之前,jvm會先在線程的棧幀中創(chuàng)建用于存儲鎖記錄的空間耘眨,然后將對象的Mark Word復(fù)制到鎖記錄中昼榛,官方稱Displaced Mark Word,再重試使用CAS將對象頭中的Mark Word替換為指向鎖記錄的指針剔难。如果成功胆屿,則獲取鎖;如果失敗偶宫,表示其他線程競爭鎖非迹,當前線程使用自旋來獲取鎖。
- 解鎖:輕量級鎖解鎖時纯趋,會使用CAS操作將Displaced Mark Word替換回對象頭中憎兽,如果成功,表示沒有競爭發(fā)生吵冒;如果失敗纯命,表示當前鎖存在競爭,鎖就會膨脹成重量級鎖痹栖。下圖展示鎖膨脹流程圖亿汞。
重量級鎖
因為自旋會消耗CPU,為了避免無用的自旋揪阿,一旦鎖升級成重量級鎖疗我,就不會再恢復(fù)到輕量級鎖狀態(tài)咆畏。當鎖處于這個狀態(tài)下,其他線程試圖獲取鎖時吴裤,都會被阻塞住旧找,當持有鎖的線程釋放鎖之后會喚醒這些線程,被喚醒的線程就會進行新一輪的奪鎖之爭嚼摩。