深入思考系列——"Synchronization on a non-final field"

場景

程序裏面使用了synchronized關鍵字,IntelliJ IDEA右邊出現了黃色條,移動到上面,提示如下:
在這裏插入圖片描述

環境

軟件 版本
JDK 1.8
IntelliJ IDEA 2019.1

原因

從提示來看,就是說,如果變量的引用發生了改變,就會導致synchronized失效,然後其他線程就會進入原本沒有結束的synchronized代碼塊。所以要使用final來修飾變量,使引用不發生改變。

嘗試

接下來讓我們來驗證一下,是否真的如提示所說。

錯誤範例

我寫了以下代碼,請各位讀者看:

public class Test1 implements Runnable{
    private Byte[] flag = new Byte[1];
    
    @Override
    public void run() {
        synchronized (flag){
            System.out.println(new Date()+" 開始休眠"+Thread.currentThread().getName());
            flag = new Byte[11];
            try {
                Thread.sleep(10000);

                System.out.println(new Date()+" 結束休眠"+Thread.currentThread().getName());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Test1 test = new Test1();
        new Thread(test).start();
        Thread.sleep(100);
        new Thread(test).start();
    }
}

如果synchronized關鍵字有生效的話,就會導致前後兩個線程會間隔10秒打印日誌。結果如下:

Fri Apr 10 22:03:43 CST 2020 開始休眠Thread-0
Fri Apr 10 22:03:43 CST 2020 開始休眠Thread-1
Fri Apr 10 22:03:53 CST 2020 結束休眠Thread-0
Fri Apr 10 22:03:53 CST 2020 結束休眠Thread-1

從結果可以看到,中間兩個線程基本沒有間隔就開始了。和我們的預期是存在差異的。那是什麼原因導致了變量的引用發生了改變嗎?就是代碼裏面的那行重新賦值的語句:

flag = new byte[11];

所以,也正如提示所說的,就是因爲變量的引用發生了改變,所以導致了synchronized關鍵字沒有生效。接下來就讓我們開始正確的嘗試階段。

正確範例

我對錯誤範例裏面的代碼進行優化,在這裏主要按照提示所說,加上final來使得對象不必改變,各位讀者跟隨我的思路來。

public class Test1 implements Runnable{
    private final Byte[] flag = new Byte[1];

    @Override
    public void run() {
        synchronized (flag){
            System.out.println(new Date()+" 開始休眠"+Thread.currentThread().getName());
            flag[0] = 11;
            // 因爲變量設置爲了final,沒有辦法進行修改。故註釋掉。
			//flag = new Byte[11];
            try {
                Thread.sleep(10000);

                System.out.println(new Date()+" 結束休眠"+Thread.currentThread().getName());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Test1 test = new Test1();
        new Thread(test).start();
        Thread.sleep(100);
        new Thread(test).start();
    }
}

結果如下:

Sun Apr 12 00:25:33 CST 2020 開始休眠Thread-0
Sun Apr 12 00:25:43 CST 2020 結束休眠Thread-0
Sun Apr 12 00:25:43 CST 2020 開始休眠Thread-1
Sun Apr 12 00:25:53 CST 2020 結束休眠Thread-1

從結果來看,這個時候的synchronized是已經生效了。因爲加了final,導致變量無法被改變引用。

總結

併發情況下面,一個變量如果是打算作爲鎖,就得考慮是否會被更新引用的情況。所以,併發情況下,還是要多思考,才能寫出質量上乘的代碼。

隨緣求贊

如果我的文章對大家產生了幫忙,可以在文章底部點個贊或者收藏;
如果有好的討論,可以留言;
如果想繼續查看我以後的文章,可以左上角點擊關注

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