【Java並行程式設計】鎖機制(三):synchronized優化方案&使用注意

2020-09-19 12:04:51

1.synchronized優化方案

1.1 鎖消除

鎖消除即刪除不必要的加鎖操作。虛擬機器器即時編輯器在執行時,對一些「程式碼上要求同步,但是被檢測到不可能存在共用資料競爭」的鎖進行消除

根據程式碼逃逸技術,如果判斷到一段程式碼中,堆上的資料不會逃逸出當前執行緒,那麼可以認為這段程式碼是執行緒安全的,不必要加鎖。

看下面這段程式:

public class SynchronizedTest {

    public static void main(String[] args) {
        SynchronizedTest test = new SynchronizedTest();

        for (int i = 0; i < 100000000; i++) {
            test.append("abc", "def");
        }
    }

    public void append(String str1, String str2) {
        StringBuffer sb = new StringBuffer();
        sb.append(str1).append(str2);
    }
}

雖然StringBuffer的append是一個同步方法,但是這段程式中的StringBuffer屬於一個區域性變數,並且不會從該方法中逃逸出去(即StringBuffer sb的參照沒有傳遞到該方法外,不可能被其他執行緒拿到該參照),所以其實這過程是執行緒安全的,可以將鎖消除。

1.2 鎖粗化

如果一系列的連續操作都對同一個物件反覆加鎖和解鎖,甚至加鎖操作是出現在迴圈體中的,那即使沒有出現執行緒競爭,頻繁地進行互斥同步操作也會導致不必要的效能損耗。

如果虛擬機器器檢測到有一串零碎的操作都是對同一物件的加鎖,將會把加鎖同步的範圍擴充套件(粗化)到整個操作序列的外部。

舉個例子:

public class StringBufferTest {
    StringBuffer stringBuffer = new StringBuffer();

    public void append(){
        stringBuffer.append("a");
        stringBuffer.append("b");
        stringBuffer.append("c");
    }
}

這裡每次呼叫stringBuffer.append方法都需要加鎖和解鎖,如果虛擬機器器檢測到有一系列連串的對同一個物件加鎖和解鎖操作,就會將其合併成一次範圍更大的加鎖和解鎖操作,即在第一次append方法時進行加鎖,最後一次append方法結束後進行解鎖。

2.synchronized使用注意事項

  • sync加在靜態方法(static)時鎖的是類,比如 sync(A.class)

  • sync的鎖粒度應該儘量小,保證原子性即可

  • synchronized遇到異常時會自動釋放鎖,需要在catch塊中做處理

  • sync只能鎖住堆,不要鎖String(方法區-常數池)

  • sync是可重入鎖,在sync塊中呼叫sync方法自動獲得鎖

  • 模擬死鎖