單例模式詳解-漸進版本

首先明確一點,單例模式的主旨在於一個類只有一個對象

1、簡單的單例模式

先寫一種最基本的:

public class SingleEasy {

    private static SingleEasy singleEasy;

    public SingleEasy(){
        System.out.println("創建了SingletonDemo...");
    }

    private static SingleEasy getInstance(){
        if (singleEasy == null){
            singleEasy = new SingleEasy();
        }
        return singleEasy;
    }
}

這個很好理解,singleEasy == null時才才創建一個對象,否則直接返回已經創建好的對象。
雖然這種方式看起來完成了單例,但是不安全,或者說他只是在單線程的情況下沒問題。原因:
寫個測試類:

public class SingleTest {
    public static void main(String[] args) {
        Thread thread1 = new Thread(new Runnable() {
            @Override
            public void run() {
                SingleEasy.getInstance();
            }
        });

        Thread thread2 = new Thread(new Runnable() {
            @Override
            public void run() {
                SingleEasy.getInstance();
            }
        });

        thread1.start();
        thread2.start();
    }
}

執行結果會出現這種情況:
在這裏插入圖片描述因爲兩個線程併發,導致的錯誤。

2.多線程模式下的單例模式

由於上面的錯誤,所以我們要給getInstance方法加synchronized,保證同一時刻只有一個線程操作此方法。

public class SingleEasy {

    private static SingleEasy singleEasy;

    private SingleEasy(){
        System.out.println("創建了SingletonDemo...");
    }

    public static synchronized SingleEasy getInstance(){
        if (singleEasy == null){
            singleEasy = new SingleEasy();
        }
        return singleEasy;
    }
}

面試中單例模式寫到這種程度應該有個七八十分的樣子。

3.雙重檢測單例模式

2中的單例基礎上基礎完善。
2中的synchronized是加在方法上的,所以鎖定的是整個方法,假如方法中的還有其他的代碼需要執行,其實就是影響效率了。所以我們只需要鎖住多線程共享的資源就可以。
先上代碼:

public class SingleEasy {

    private static volatile SingleEasy singleEasy;

    private SingleEasy(){
        System.out.println("創建了SingletonDemo...");
    }

    public static SingleEasy getInstance(){
        if (singleEasy == null){
            synchronized(SingleEasy.class){
                if (singleEasy == null){
                    singleEasy = new SingleEasy();
                }
            }
        }
        return singleEasy;
    }
}

爲了更加保險,做了兩次判斷。這麼寫就算比較完善的單例了。值得注意的是volatile關鍵字。因爲即使我們現在線程安全但人有可能創建兩個對象,因爲jvm內存模型的關係。用了volatile關鍵字就可以讓內存內容對線程直接可見,略過工作內存。

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