【單例模式】java實現


概述:確保一個類只有一個實例,而且自行實例化並向整個系統提供這個實例。

關鍵點:

  1. 構造函數不對外開放,一般為private。
  2. 通過一個靜態方法或者枚舉返回單例類對象。
  3. 確保單例類的對象有且只有一個,尤其在多線程情況下。
  4. 確保單例類對象在反序列化時不會重新構建對象

(1)餓漢模式

餓漢式單例模式(在類加載時就完成了初始化,所以類加載較慢,但獲取對象的速度快)

public class EagerSingle {
    //餓漢模式單例
    //在類加載時就完成了初始化,所以類加載較慢,但獲取對象的速度快
    private static EagerSingle single = new EagerSingle();//靜態私有成員,已初始化

    private EagerSingle() {
        //私有構造函數
    }

    public static EagerSingle getInstance() {//靜態,不用同步(類加載時已初始化,不會有多線程的問題)
        return single;
    }
}

(2)懶漢模式

懶漢模式聲明一個靜態對象,並且在用戶第一次調用getInstance時進行初始化。

public class LazySingleton {
    //懶漢模式單例
    //比較懶,在類加載時不創建實例,因此類加載熟讀快,但運行時獲取對象速度慢
    private static LazySingleton instance;//靜態私有成員,沒有初始化

    private LazySingleton() {
        //私有構造函數
    }

    public static synchronized LazySingleton getInstance() {//靜態、同步、公開訪問
        if (instance == null) {
            instance = new LazySingleton();
        }
        return instance;
    }
}

 

synchronized關鍵字保證了同步,在多線程情況下單例的唯一性。

存在問題:即使instance已經存在,每次調用getInstance依然會進行同步,這樣就會消耗不必要的資源。

總結:懶漢模式的優點是只有在使用時才會實例化單例對象,在一定程度上節約了資源;缺點是第一次加載時需要進行實例化,反應稍慢;最大問題是每次調用都會進行同步嗎,造成不必要的同步開銷。這種模式一般不建議使用。

(3)Double Check Lock(DCL)雙重校驗鎖

DCL方式實現單例的優點是既能在需要時才初始化單例,又能保證線程安全,且單例對象初始化后調用getInstance不進行同步鎖。

public class DCLSingleton {
    //Double Check Lock單例模式
    //懶漢模式的改進
    //但仍然存在隱患
    private static DCLSingleton instance = null;

    private DCLSingleton() {
    }

    public static DCLSingleton getInstance() {
        if (instance == null) {//第一層判斷主要是為了避免不必要的同步
            synchronized (DCLSingleton.class) {
                if (instance == null) {//第二層判空是為了在null情況下創建實例
                    instance = new DCLSingleton();
                }
            }
        }
        return instance;
    }
}

亮點在getInstance方法上,有兩次判空。第一層判斷主要是為了避免不必要的同步,第二層判空是為了在null情況下創建實例。

 

  • 執行下面這行代碼
single = new Singleton();

實際上並不是一個原子操作,這句代碼實際做了3件事

  1. 給Singleton的實例分配內存;
  2. 調用Singleton()的構造函數,初始化成員字段
  3. 將instance對象指向分配的內存空間(此時instance已經不是null了)

 

問題:但由於java編譯器允許處理器亂序執行,上述順序2、3是不能保證的,可能是1-2-3也可能是1-3-2;如果是后者,3執行了已經非空,再走2會出現問題,這就是DCL失效

解決: volatile關鍵字

//    private static DCLSingleton instance = null;
    private volatile static DCLSingleton instance = null;

 

只需要加上volatile關鍵字,如上述代碼操作就可以保證instance對象每次都是從主內存中讀取的,就可以采用DCL來完成單例模式了。當然,volatile或多或少會影響到性能,但考慮到程序的正確性,犧牲點性能還是值得的。

總結:

  • 優點:資源利用率高,第一次執行getInstance時單例對象才會被實例化,效率高。
  • 缺點:第一次加載時反應稍慢;由於java內存模型的原因偶爾會失敗,在高並發環境下也有一定的缺陷,雖然概率很小。
  • DCL模式是使用最多的單例實現方式

 

(4)靜態內部類單例模式

public class InnerSingleton {
    private InnerSingleton() {
    }

    public static InnerSingleton getInstance() {
        return InnerSingletonHolder.instance;
    }

    /**
     * 靜態內部類
     */
    private static class InnerSingletonHolder {
        private static final InnerSingleton instance = new InnerSingleton();
    }
}

 

總結:第一次加載InnerSingleton類時並不會初始化instance,只有在第一次調用InnerSingleton的getInstance方法時才會導致instance被初始化。因此,第一次調用getInstance方法會導致虛擬機加載InnerSingleton類,這種方法不僅能保證線程安全,也能夠保證單例對象的唯一性,同時也延遲了單例的實例化,所以這也是一種推薦的單例模式實現方法

 

(5)枚舉單例

public enum EnumSingleton {
    INSTANCE;

    public void doSomething() {
        //do sth ...
    }
}

 

震驚?沒錯!就是枚舉!

  • 寫法簡單;枚舉在java種與普通類是一樣的,不僅能夠有字段,還能夠有自己的方法。最重要的是默認枚舉實例的創建時線程安全的,並且在任何情況下都是一個單例。
  • 為什么這么說呢?  在上述的集中單例模式實現種,在一個情況下他們都會出現重新創建對象的情況,那就是反序列化

補充: 通過序列化可以將一個單例的實例對象寫到磁盤,然后再讀回來,從而有效的獲取一個實例。即使構造函數是私有的,反序列化時依然可以通過特殊的途徑去創建類的一個新的實例,相當於調用該類的構造函數。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM