設計模式:創建型->單例模式

創建型模式:對象實例化的模式,創建型模式用於解耦對象的實例化過程。
 
以下參考這篇文章:https://zhuanlan.zhihu.com/p/160842212
 

什麼是單例模式

單例模式是指在內存中只會創建且僅創建一次對象的設計模式。在程序中多次使用同一個對象且作用相同時,爲了防止頻繁地創建對象使得內存飆升,單例模式可以讓程序僅在內存中創建一個對象,讓所有需要調用的地方都共享這一單例對象。
 

兩種類型

    • 懶漢式:在真正需要使用對象時纔去創建該單例類對象
    • 餓漢式:在類加載時已經創建好該單例對象,等待被程序使用

其實比較好理解的

餓漢式

餓漢式在類加載時已經創建好該對象,在程序調用時直接返回該單例對象即可。我們目前可以簡單認爲在程序啓動時,這個單例對象就已經創建好了

public class Singleton {
    //餓漢式
    private static Singleton singleton=new Singleton();
    private Singleton() 
    { 
        
    }
    public Singleton getInstance()
    {
        return singleton;
    }

}

構造方法私有,這樣就不能在外部new出對象,只能使用getInstance獲取對象。

優缺點:

懶漢式

public class Singleton {
    //懶漢式
    private static Singleton singleton;
    private Singleton()
    {}
    public Singleton getInstance()
    {
        if(singleton==null)
        {
            singleton=new Singleton();
        }
        return singleton;
    }
}

最基礎的寫法,類加載的時候沒有實例化,使用時才new

 

但這種寫法會出現併發問題

如果兩個線程同時判斷singleton爲空,那麼它們都會去實例化一個Singleton對象,這就變成雙例了。所以,我們要解決的是線程安全問題。

 加上同步方法或者同步代碼塊

public static synchronized Singleton getInstance() {
    if (singleton == null) {
        singleton = new Singleton();
    }
    return singleton;
}
// 或者
public static Singleton getInstance() {
    synchronized(Singleton.class) {   
        if (singleton == null) {
            singleton = new Singleton();
        }
    }
    return singleton;
}

但這兩種方式都不好,我們要鎖住的是 創建對象這一過程

獲取對象這個過程沒必要加鎖

public static Singleton getInstance() {
    if (singleton == null) {  // 線程A和線程B同時看到singleton = null,如果不爲null,則直接返回singleton
        synchronized(Singleton.class) { // 線程A或線程B獲得該鎖進行初始化
            if (singleton == null) { // 其中一個線程進入該分支,另外一個線程則不會進入該分支
                singleton = new Singleton();
            }
        }
    }
    return singleton;
}

這種寫法相對較好:

  • 之所以進入判斷後還要再判斷一次,是因爲可能其他線程已經把它實例化了
  • 鎖住的爲什麼是 Singleton.class 鎖住一個類

 

指令重排問題

創建一個對象,在JVM中會經過三步:

(1)爲singleton分配內存空間

(2)初始化singleton對象

(3)將singleton指向分配好的內存空間

指令重排序是指:JVM在保證最終結果正確的情況下,可以不按照程序編碼的順序執行語句,儘可能提高程序的性能

在這三步中,第2、3步有可能會發生指令重排現象,創建對象的順序變爲1-3-2,會導致多個線程獲取對象時,有可能線程A創建對象的過程中,執行了1、3步驟,線程B判斷singleton已經不爲空,獲取到未初始化的singleton對象,就會報NPE異常。使用volatile關鍵字可以防止指令重排序。

 

 

public class Singleton {
    
    private static volatile Singleton singleton;
    
    private Singleton(){}
    
    public static Singleton getInstance() {
        if (singleton == null) {  // 線程A和線程B同時看到singleton = null,如果不爲null,則直接返回singleton
            synchronized(Singleton.class) { // 線程A或線程B獲得該鎖進行初始化
                if (singleton == null) { // 其中一個線程進入該分支,另外一個線程則不會進入該分支
                    singleton = new Singleton();
                }
            }
        }
        return singleton;
    }
    
}

最終代碼,把對象用volatile修飾

 

單例模式使用場景

創建的一個對象需要消耗的資源過多,比如 I/O 與數據庫的連接等。

 

 

 

 

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