1. 程式人生 > >Java synchronized簡析

Java synchronized簡析

前言

在Java併發程式設計裡synchronized關鍵字一直都有著重要的地位,在Java中任何物件都可以作為鎖物件,對於普通的方法鎖定的是當前例項物件,對於靜態方法鎖定的是當前的類物件,對於同步程式碼塊鎖定的是括號裡的物件。最開始synchronized的實現是採用底層作業系統的互斥鎖實現,這種鎖需要在核心和使用者態之間切換效率非常的低,不過在1.6版本引入了大量的優化,使得synchronized的效率有了很大的提高,現在就來總結一下synchronized鎖實現的機制。

JDK優化

JDK的優化主要包含鎖粗化、鎖消除、偏向鎖、輕量級鎖和自適應自旋鎖,下面通過簡單地說明這些鎖優化具體做了什麼。

private String getString() {
    StringBuffer stringBuffer = new StringBuffer();
    for (int i = 0; i < 10; i++) {
        stringBuffer.append(i);
    }
    return stringBuffer.toString();
}

上面的程式碼實現了向StringBuffer物件中新增多個不同的數字,我們知道StringBuffer是執行緒安全的類實現,它的每個方法都會在使用之前加鎖,實際上完全沒有必要每一步都請求鎖,只要在迴圈的外面新增一個鎖,裡面的每一步就不必在申請鎖,這種優化就是鎖粗化,減少多個不必要緊密連線在一起的鎖,將多個連續鎖擴充套件成一個範圍更大的鎖。

如果再仔細的檢視上面的原始碼會發現StringBuffer物件僅僅是一個本地變數,不管它在哪個執行緒中呼叫由於物件始終只處於這個執行緒的呼叫棧中並不會和其他執行緒共享這個變數,完全沒有必要加上鎖操作,最終執行程式碼就會去掉這個synchronized鎖,這就是鎖消除優化。

偏向鎖

偏向鎖的引入者發現大多數情況下鎖不僅不存在多執行緒競爭,而且總是由同一個執行緒獲得,偏向鎖能夠有效的降低獲取鎖的代價。Java的物件在頭部有一個Mark Word欄位,偏向鎖的資料就記錄在其中。

這裡寫圖片描述

獲取鎖:檢測當前物件MarkWord是否設定為當前執行緒偏向鎖,是繼續執行程式碼,否則測試Mark Word中偏向鎖是否設定如果沒有設定使用CAS競爭鎖,如果設定了就使用CAS將物件頭裡的偏向鎖指向當前執行緒。

撤銷鎖:只有當其他執行緒競爭鎖的時候才會出現釋放鎖,撤銷的時候在沒有正在執行的位元組碼的時候首先測試擁有該偏向鎖的執行緒是否處於活動狀態,如果是就讓鎖偏向於其他執行緒或者設定鎖不適合作為偏向鎖,如果沒有活動就清空偏向鎖資料。如果被判定不適合做偏向鎖就會發生瑣升級,使用輕量級鎖來做同步操作。

輕量級鎖

輕量級鎖基於如下假設:在真實的情況下程式中大部分同步程式碼都處於單執行緒執行,也就是無鎖競爭狀態,這時應該避免使用作業系統層面的重量級互斥鎖,而是使用CAS完成鎖的獲取和釋放操作。當鎖存在競爭的情況下,執行CAS失敗的執行緒會繼續做自旋操作,儘量不進入操作作業系統的底層鎖。如果CAS失敗多次才會呼叫作業系統的底層鎖進入阻塞狀態。

變數名 作用
Owner 初始為null,當有執行緒獲取當前鎖物件,內容為執行緒的標識,瑣釋放後為null
EntryQ 關聯作業系統的互斥鎖,阻塞所有無法獲取當前鎖的執行緒
RfThis 阻塞在重量級鎖上的執行緒個數
Nest 用來計數重入鎖的計數
HashCode 儲存從Mark Word拷貝過來的物件HashCode
Candidate 為避免多個執行緒同時競爭同一個鎖,引起不必要的上下文切換消耗效能;如果為1代表只喚醒下一個執行緒,否則就喚醒所有等待執行緒

獲取鎖:首先測試物件是否已經和MonitorRecord關聯,如果沒有關聯需要做膨脹處理,也就是使用物件資料初始化MonitorRecord就是把MonitorRecord的Owner欄位設定為當前執行緒標識,然後將MonitorRecord的地址使用CAS設定到物件頭的LockWord,如果設定成功獲取輕量級鎖成功;如果獲取失敗說明有其他執行緒也再做獲取鎖操作,之後需要使用之前執行緒膨脹的MonitorRecord的owner和當前執行緒標識做CAS,如果成功表明獲取鎖成功,否則需要繼續自旋操作;如果多次自旋依然無法獲取鎖,這時就升級鎖到重量級鎖也就是作業系統互斥鎖,在該鎖上做阻塞等待。

釋放鎖:首先檢查物件是否膨脹並且執行緒是鎖的擁有者,不是則丟擲異常,如果RecordMonitor的nest屬性大於1需要減去1,如果等於1需要檢查rfThis屬性檢視是否有執行緒在作業系統互斥鎖上等待獲取鎖,如果不為0就喚醒等待執行緒,否則就縮小一個物件並且將物件頭部記錄的LockWord和之前的hashcode換回來,接觸RecordMonitor和物件的關係。