大話設計模式之單例模式詳解

應用場景

在學習單例設計模式前,我想應該先去了解這種模式是在什麼樣的背景下產生的,這有利於後續學習時的理解。記得我自己最先真正使用到單例模式,是在進行數據庫連接的時候,當時在做原生項目的時候,因爲需要經常訪問數據庫,就會常常打開和關閉數據庫連接,這種方式會導致項目的效率下降。在這種場景下,如果使用一個類,來對數據庫資源的連接進行封裝,保證在整個項目中,只有一個該類提供的對象,就可以避免上述的問題。所以,單例模式的作用也就體現出來了,當然它的應用場景還有許多,例如項目日誌輸出,統計網站在線人數等。

定義

單例模式最初的定義出現於《設計模式》(艾迪生維斯理,1994):“保證一個類僅有一個實例,並提供一個訪問它的全局訪問點。”在Java中單例模式的定義是:“一個類有且僅有一個實例,並且自行實例化向整個系統提供。”

實現思路

單例模式分爲餓漢式和懶漢式兩種,兩者實現的思路相同:
1.在單例類中,提供一個私有靜態屬性,用於接收實例化對象;
2.在單例類中,構造方法必須私有化,防止在類外進行實例化;
3.在單例類中,向類外提供一個獲取類對象的公有靜態方法。

代碼實現

餓漢式單例模式實現

餓漢式單例模式:在系統加載類的時候就會創建類的對象,並保存在類中。它的特點是線程安全,以空間換時間。

public class Singleton {
    //1、私有靜態屬性
    private static Singleton instance = new Singleton();
    //2、私有構造方法
    private Singleton() {
        System.out.println("***創建Singleton類對象***");
    }
    //3、公有靜態方法
    public static Singleton getInstance() {
        return instance;
    }
    //測試
    public static void main(String[] args) {
        Singleton s1 = Singleton.getInstance();
        Singleton s2 = Singleton.getInstance();
        System.out.println(s1 == s2);
    }
}
//程序執行結果
***創建Singleton類對象***
true

懶漢式單例模式實現

懶漢式單例模式:在系統加載類的時候不創建類的對象,直到第一次使用類的對象時纔會進行創建。它的特點是線程不安全,以時間換空間。

public class Singleton {
    //1、私有靜態屬性
    private static Singleton instance = null;
    //2、私有構造方法
    private Singleton() {
        System.out.println("***創建Singleton類對象***");
    }
    //3、公有靜態方法
    public static Singleton getInstance() {
        //判斷類對象是否爲空,若爲空則創建類對象
        if(instance == null){
            instance = new Singleton();
        }
        return instance;
    }
    //測試
    public static void main(String[] args) {
        Singleton s1 = Singleton.getInstance();
        Singleton s2 = Singleton.getInstance();
        System.out.println(s1 == s2);
    }
}
//程序執行結果
***創建Singleton類對象***
true

懶漢式單例模式是存在線程風險的,下面我們來測試一下,是不是像說的那樣真的存在線程風險?

public class Singleton {
    //1、私有靜態屬性
    private static Singleton instance = null;
    //2、私有構造方法
    private Singleton() {
        System.out.println("【" + Thread.currentThread().getName() + "】***創建Singleton類對象***");
    }
    //3、公有靜態方法
    public static Singleton getInstance() {
        //判斷類對象是否爲空,若爲空則創建類對象
        if(instance == null){
            instance = new Singleton();
        }
        return instance;
    }
    public void print() {
        System.out.println("**執行print()方法**");
    }
    //測試
    public static void main(String[] args) {
        //創建三個線程對象並運行
        for(int i = 0; i < 3; i++){
           new Thread(() -> {
               //在線程運行過程中,獲取Singleton類對象
               Singleton.getInstance().print();
           }, "線程對象 - " + i).start();
        }
    }
}
//程序執行結果
【線程對象 - 0***創建Singleton類對象***
【線程對象 - 1***創建Singleton類對象***
**執行print()方法**
【線程對象 - 2***創建Singleton類對象***
**執行print()方法**
**執行print()方法**

通過程序的執行結果,發現當有了若干個線程對象,當前的程序就可以產生多個Singleton類的對象。顯然,這不符合單例模式的特點,因爲單例模式要求在程序的整體運行中只產生一個實例化對象。造成上述問題的關鍵是,代碼本身出現了不同步的情況,而解決的核心就在於進行同步處理。

懶漢式單例模式改進

實現代碼同步,我們需要使用到sychronized關鍵字。改進方法一,直接對公有靜態方法做同步處理:

public static synchronized Singleton getInstance() {
    //判斷類對象是否爲空,若爲空則創建類對象
    if(instance == null){
        instance = new Singleton();
    }
    return instance;
}
//程序執行結果
【線程對象 - 0***創建Singleton類對象***
**執行print()方法**
**執行print()方法**
**執行print()方法**

按照上述方法,我們的確實現了單例模式的要求,保證在程序整體運行過程中,單例類只提供一個實例化的對象。但是,使用這種方法做同步處理,效率會比較低,因爲在整個方法中只有一個地方需要做同步處理,下面我們使用同步代碼塊進行更合理的同步處理。

public static Singleton getInstance() {
    //判斷類對象是否爲空,若爲空則創建類對象
    if(instance == null){
        //同步代碼塊,靜態方法中不允許使用this關鍵字,這裏使用類的Class對象
        synchronized(Singleton.class){
            if(instance == null){
                instance = new Singleton();
            }
        }
    }
    return instance;
}
//程序執行結果
【線程對象 - 0***創建Singleton類對象***
**執行print()方法**
**執行print()方法**
**執行print()方法**

通過對“判斷instance對象是否爲空”作同步處理,這種方法可以減小同步產生的代價,使程序的執行效率更高一些。
懶漢式單例模式實現,使用這種方法也被稱爲DCL(雙重檢查加鎖)。另外,爲了在程序性能上再做一點提升,可以在單例模式的屬性聲明上使用volatile關鍵字,例如:

private static volatile Singleton instance = null;

到達文末了,十分感謝小夥伴們的閱讀哈,如果您對我的文章有什麼意見或者建議,也歡迎您來進行評論,私信我也可以哈!
代碼如詩,小夥伴們和我一起努力加油,做持續學習者!:)

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