併發編程(JAVA版)-------------(四)

本文承接併發編程(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會這樣覺得,自己確實偏向錯了,根本就不該偏向,於是整個類的所有對象都會變爲不可偏向的,新建的對象也是不可偏向的

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章