1. 程式人生 > >設計模式(1)——單例模式

設計模式(1)——單例模式

虛擬 span 源碼 加載 定義 實例變量 不能 內存 而且

在Java開發過程中,很多場景下都會碰到或要用到單例模式,在設計模式裏也是經常作為指導學習的熱門模式之一,相信每位開發同事都用到過。我們總是沿著前輩的足跡去做設定好的思路,往往沒去探究為何這麽做,所以這篇文章對單例模式做了詳解。

一、單例模式定義

單例模式確保某個類只有一個實例,而且自行實例化並向整個系統提供這個實例。

二、單例模式的特點:

  1. 單例模式類只能有一個實例
  2. 單例類必須自己創建唯一實例
  3. 給其他類訪問提供接口

單例模式保障了對象的唯一性,在計算機系統中,線程池、緩存、日誌對象、對話框、打印機、顯卡的驅動程序對象常被設計成單例。

這些應用都或多或少具有資源管理器的功能。每臺計算機可以有若幹個打印機,但只能有一個Printer Spooler,以避免兩個打印作業同時輸出到打印機中。

三、保證線程安全

一方面在獲取單例的時候,要保證不能產生多個實例對象,後面會詳細講到五種實現方式;

另一方面,在使用單例對象的時候,要註意單例對象內的實例變量是會被多線程共享的,推薦使用無狀態的對象,不會因為多個線程的交替調度而破壞自身狀態導致線程安全問題,比如我們常用的VO,DTO等(局部變量是在用戶棧中的,而且用戶棧本身就是線程私有的內存區域,所以不存在線程安全問題)。

四、實現單例模式的方式

1.餓漢式單例(立即加載方式)

技術分享圖片
// 餓漢式單例
public class Singleton1 {
    // 私有構造
    private Singleton1() {}

    private static Singleton1 single = new Singleton1();

    // 靜態工廠方法
    public static Singleton1 getInstance() {
        return single;
    }
}
技術分享圖片

餓漢式單例在類加載初始化時就創建好一個靜態的對象供外部使用,除非系統重啟,這個對象不會改變,所以本身就是線程安全的。

Singleton通過將構造方法限定為private避免了類在外部被實例化,在同一個虛擬機範圍內,Singleton的唯一實例只能通過getInstance()方法訪問。(事實上,通過Java反射機制是能夠實例化構造方法為private的類的,那基本上會使所有的Java單例實現失效。此問題在此處不做討論,姑且閉著眼就認為反射機制不存在。)

2.懶漢式單例(延遲加載方式)

技術分享圖片
// 懶漢式單例
public class Singleton2 {
// 私有構造 private Singleton2() {}
private static Singleton2 single = null;
public static Singleton2 getInstance() { if(single == null){ single = new Singleton2(); } return single; } }
技術分享圖片

該示例雖然用延遲加載方式實現了懶漢式單例,但在多線程環境下會產生多個single對象,如何改造請看以下方式:

使用synchronized同步鎖

技術分享圖片
public class Singleton3 {
    // 私有構造
    private Singleton3() {}

    private static Singleton3 single = null;

    public static Singleton3 getInstance() {
        
        // 等同於 synchronized public static Singleton3 getInstance()
        synchronized(Singleton3.class){
          // 註意:裏面的判斷是一定要加的,否則出現線程安全問題
            if(single == null){
                single = new Singleton3();
            }
        }
        return single;
    }
}
技術分享圖片

在方法上加synchronized同步鎖或是用同步代碼塊對類加同步鎖,此種方式雖然解決了多個實例對象問題,但是該方式運行效率卻很低下,下一個線程想要獲取對象,就必須等待上一個線程釋放鎖之後,才可以繼續運行。

技術分享圖片
public class Singleton4 {
    // 私有構造
    private Singleton4() {}

    private static Singleton4 single = null;

    // 雙重檢查
    public static Singleton4 getInstance() {
        if (single == null) {
            synchronized (Singleton4.class) {
                if (single == null) {
                    single = new Singleton4();
                }
            }
        }
        return single;
    }
}
技術分享圖片

使用雙重檢查進一步做了優化,可以避免整個方法被鎖,只對需要鎖的代碼部分加鎖,可以提高執行效率。

3.靜態內部類實現

技術分享圖片
public class Singleton6 {
    // 私有構造
    private Singleton6() {}

    // 靜態內部類
    private static class InnerObject{
        private static Singleton6 single = new Singleton6();
    }
    
    public static Singleton6 getInstance() {
        return InnerObject.single;
    }
}
技術分享圖片

靜態內部類雖然保證了單例在多線程並發下的線程安全性,但是在遇到序列化對象時,默認的方式運行得到的結果就是多例的。這種情況不多做說明了,使用時請註意。

4.static靜態代碼塊實現

技術分享圖片
public class Singleton6 {
    
    // 私有構造
    private Singleton6() {}
    
    private static Singleton6 single = null;

    // 靜態代碼塊
    static{
        single = new Singleton6();
    }
    
    public static Singleton6 getInstance() {
        return single;
    }
}
技術分享圖片

5.內部枚舉類實現

技術分享圖片
public class SingletonFactory {
    
    // 內部枚舉類
    private enum EnmuSingleton{
        Singleton;
        private Singleton8 singleton;
        
        //枚舉類的構造方法在類加載是被實例化 
        private EnmuSingleton(){
            singleton = new Singleton8();
        }
        public Singleton8 getInstance(){
            return singleton;
        }
    }
    public static Singleton8 getInstance() {
        return EnmuSingleton.Singleton.getInstance();
    }
}

class Singleton8{
    public Singleton8(){}
}
技術分享圖片

以上就是本文要介紹的所有單例模式的理解和實現,相信這篇文章能讓大家更清楚的理解單例模式,希望大家有問題可以探討,多多指教!

備註:本文單例實現部分,實例源碼參照《Java多線程編程核心技術》-(高洪巖)一書中第六章的學習案例撰寫。

獲取文章中完整代碼示例可訪問github:https://github.com/fugaoyang/per-practice

設計模式(1)——單例模式