本文承接併發編程(JAVA版)-------------(一)
本文承接併發編程(JAVA版)-------------(二)
本文承接併發編程(JAVA版)-------------(三)
文章目錄
synchronized原理進階
鎖膨脹
如果在嘗試加輕量級鎖的過程種,CAS操作無法成功,這時一種情況就是有其他線程爲此對象加上了輕量級鎖(有競爭),這時需要進行鎖膨脹,將輕量級鎖變爲重量級。
static Object obj = new Object();
public static void method1() {
synchronized(obj) {
// 同步代碼快,臨界區
}
}
- 當Thread-1進行輕量級加鎖是Thread-0已經對該對象加了輕量級鎖。
- 這時Thread-1加輕量級鎖失敗,進入鎖膨脹流程
- 即爲Object對象申請Monitor鎖,讓Object指向重量級鎖地址
- 然後自己進入Monitor的EntryList BLOCKED進行阻塞
- 當Thread-0退出同步塊解鎖時,使用cas將Mark Word的值恢復給對象頭。這時會進入重量級解鎖流程,即按照Monitor地址找到Monitor對象,將設置Owner爲null,喚醒EntryList中BLOCKED線程。
自旋優化
重量級鎖競爭的時候,還可以使用自旋來進行優化,如果當前線程自旋成功(即這時持鎖線程已經退出了同步塊,釋放了鎖),這時當前線程就可以避免阻塞。
自旋重試成功的情況:
線程1(CPU1上) | 對象Mark | 線程2(CPU2上) |
---|---|---|
- | 10(重量鎖) | - |
訪問同步代碼塊 | 10(重量級)重量鎖指針 | - |
成功(加鎖) | 10(重量級鎖)重量鎖指針 | - |
執行同步塊 | 10(重量級鎖)重量鎖指針 | - |
執行同步塊 | 10(重量級鎖)重量鎖指針 | 訪問同步塊,獲取monitor |
執行同步塊 | 10(重量級鎖)重量鎖指針 | 自旋重試 |
執行完畢 | 10(重量級鎖)重量鎖指針 | 自旋重試 |
成功(解鎖) | 01(無鎖) | 自旋重試 |
- | 10(重量鎖)重量鎖指針 | 成功(加鎖) |
- | 10(重量鎖)重量鎖指針 | 執行同步塊 |
- | … | … |
自旋重試失敗的情況:
線程1(CPU1上) | 對象Mark | 線程2(CPU2上) |
---|---|---|
- | 10(重量鎖) | - |
訪問同步代碼塊 | 10(重量級)重量鎖指針 | - |
成功(加鎖) | 10(重量級鎖)重量鎖指針 | - |
執行同步塊 | 10(重量級鎖)重量鎖指針 | - |
執行同步塊 | 10(重量級鎖)重量鎖指針 | 訪問同步塊,獲取monitor |
執行同步塊 | 10(重量級鎖)重量鎖指針 | 自旋重試 |
執行同步塊10(重量級鎖)重量鎖指針 | 自旋重試 | |
執行同步塊10(重量級鎖)重量鎖指針 | 自旋重試 | |
執行同步塊10(重量級鎖)重量鎖指針 | 阻塞 | |
- | … | … |
- 在Java6之後自旋鎖時自適應的,比如對象剛剛的一次自旋操作成功過,那麼認爲這次自旋成功的可能性會高,就多自旋幾次;反之,就少自旋設值不自旋,總之,比較智能。
- 自旋轉會佔用CPU時間,單核CPU自旋就是浪費,多核CPU自旋才能發揮優勢。
- java7之後不能控制是否開啓自旋功能
偏向鎖
輕量級鎖在沒有競爭時(就自己這個線程),每次重入仍然需要執行CAS操作。
Java6中引入了偏向鎖來進一步優化:只有第一次使用CAS將線程ID設置到對象的Mark Word頭,之後發現這個線程ID是自己的就表示沒有競爭,不用重新CAS,以後只要不發生競爭,這個對象就歸該線程所有。
偏向狀態
一個對象創建時:
- 如果開啓了偏向鎖(默認開啓),那麼對象創建後,markword值爲0x05即最後3位爲101,這時它的thread、epoch、age都爲0
- 偏向鎖是默認是延遲的,不會在程序啓動時立即生效如果想避免延遲,可以加VM參數 - xx:BiasedLockingStartupDelay=0來禁用延遲
- 如果沒有開啓偏向鎖,那麼對象創建後,markword值爲0x01即最後三位爲001,這時它的hashcode、age都爲0,第一次用到hashcode時纔會賦值
禁用偏向鎖
在測試代碼運行時在添加VM參數 -xx:-UseBiasedLocking禁用偏向鎖
還有一種方法
Pig p = new Pig;
p.hashCode();會禁用這個對象的偏向鎖
撤銷 — 調用對象hashCode
調用了對象的hashCode,但偏向鎖的對象MarkWord中存儲的時線程id,如果調用hashCode會導致偏向鎖被撤銷。
- 輕量級鎖會在記錄中記錄hashCode
- 重量級鎖會在Monitor中記錄hashCode
在調用hashCode後使用偏向鎖,記住去掉-xx:-UseBiasedLocking
撤銷 — 其它線程使用對象
當有其它線程使用偏向鎖對象時,會將偏向鎖升級爲輕量級鎖
撤銷 — 調用wait/notify
批量重偏向
如果對象雖然被多個線程訪問,但沒有競爭,這時偏向了線程T1的對象仍有機會重新偏向T2,重偏向重置對象的ThreadID
當撤銷偏向鎖閾值超過20次後,jvm會這樣覺得,我是不是偏向錯了呢,於是會在給這些對象加鎖時重新偏向至加鎖線程
批量撤銷
當撤銷偏向鎖閾值超過40次後,jvm會這樣覺得,自己確實偏向錯了,根本就不該偏向,於是整個類的所有對象都會變爲不可偏向的,新建的對象也是不可偏向的