設計模式--單例(Singleton Pattern)

定義

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

使用場景

避免產生多個對象消耗過多的資源,或者某種類型的對象只有一個。函數

一、餓漢模式

public static class Singleton {
    private static final instance Singleton = new Singleton();
    
    private Singleton() { // 私有構造函數
    }
    
    public static Singleton getInstance() {
        return instance;
    }
}
複製代碼

instance是靜態成員變量,在聲明時就被實例化。優化

二、懶漢模式

public static class Singleton {
    private static final instance Singleton = null;
    
    private Singleton() { // 私有構造函數
    }
    
    public static synchronized Singleton getInstance() {
        if(instance == null) {
            instance = new Singleton()
        }
        return instance;
    }
}
複製代碼

instance只是在初次調用時初始化,synchronized,每次調用getInstance()方法都會進行同步,這樣會消耗沒必要要的資源,這也是懶漢模式最大的問題。ui

三、Double Check Lock(DCL)

public static class Singleton {
    private static final instance Singleton = null;
    
    private Singleton() { // 私有構造函數
    }
    
    public static Singleton getInstance() {
        if(instance == null) {
            synchronized(Singleton.class) {
                if(instance == null) {
                    instance = new Singleton()
                }
            }
        }
        return instance;
    }
}
複製代碼

DCL有兩次判空,第一次判空,是爲了不沒必要要的同步;第二次判空,是爲了初次初始化時,只初始化一次。

spa

instance = new Singletone(); 這行代碼最終會編譯成多條彙編指令,它大體作了三件事:線程

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

DCL失效狀況:
因爲Java編譯器容許處理器亂序執行,以及JDK1.5以前JMM(Java Memory Model)中Cache、寄存器到內存回寫順序的規定,上面第二和第三的順序是沒法保證的。也就是說有可能JVM會爲新的Singleton實例分配空間,而後直接賦值給instance成員,而後再去初始化這個Singleton實例,這樣就可能出錯了。咱們以A、B兩個線程爲例:
code

  1. A、B線程同時進入了第一個if判斷
  2. A首先進入synchronized塊,因爲instance爲null,因此它執行instance = new Singleton();
  3. 因爲JVM內部的優化機制,JVM先畫出了一些分配給Singleton實例的空白內存,並賦值給instance成員(注意此時JVM沒有開始初始化這個實例),而後A離開了synchronized塊。
  4. B進入synchronized塊,因爲instance此時不是null,所以它立刻離開了synchronized塊並將結果返回給調用該方法的程序。
  5. 此時B線程打算使用Singleton實例,卻發現它沒有被初始化,因而錯誤發生了。

在JDK1.5以後,SUN公司調整了JVM,增長了volatile關鍵字,只需將instance的定義改爲private volatile static Singleton instance = null就能夠保存instance對象每次都是從主內存總讀取。對象

4.靜態內部類單例模式

public static class Singleton {
    private Singleton() { // 私有構造函數
    }
    
    private static class SingletonHolder {
        private static final instance Singleton = new Singleton();
    }
    
    public static synchronized Singleton getInstance() {
        return SingletonHolder.instance;
    }
}
複製代碼

當第一次加載Singleton類時,並不會初始化,只有當第一次調用getInstance()方法,會致使虛擬機加載SingletonHolder類,此時纔是初始化。利用的Java類加載機制接口

不一樣的類裝載器,可能會致使產生多個單例對象:內存

public static Class getClass(String className) throws ClassNotFoundException {
    ClassLoader cl = Thread.currentThread().getContextClassLoader();
    if (cl == null) {
        cl = Singleton.class.getClassLoader();
    }
    return cl.loadClass(className);
}
複製代碼

若是Singleton實現了Serializable接口,反序列化時可能產生多個實例對象:

private Object readResolve() { // 重寫readResolve方法
    return instance;
}
複製代碼
相關文章
相關標籤/搜索