【設計模式】併發編程下的單例模式

一、餓漢單例

1. 靜態變量實現

public class Singleton {
    private static Singleton instance = new Singleton();
    private Singleton() {}
    
    public static Singleton getInstance() {
        return instance;
    }
}

這種實現方式基於class loader機制避免了多線程的同步問題。不過,instance在類裝載時就實例化,雖然導致類裝載的原因有很多種,在單例模式中大多數都是調用getInstance方法, 但是也不能確定有其他的方式(或者其他的靜態方法)導致類裝載,這時候初始化instance顯然沒有達到lazy loading的效果。

2. 靜態代碼塊實現

public class Singleton {
    private Singleton instance = null;
    private Singleton() {}
    static {
        instance = new Singleton();
    }
    
    public static Singleton getInstance() {
        return this.instance;
    }
}

這種實現方式表面上看起來與上面差別挺大的,其實跟上面的差別不大,都是在類初始化即實例化instance。

二、懶漢單例

1. 基本實現,線程不安全

public class Singleton {
    
    private static Singleton instance;
    private Singleton() {}
    
    public static Singleton getInstance() {
        if (instance == null) {
			instance = 	new Singleton();
        }
        return instance;
    }
}

這種是最基本的lazy loading實現的單例模式,但是缺點很明顯,在多線程下顯然不能安全工作。

2. synchronized同步靜態方法,線程安全

public class Singleton {
    
    private static Singleton instance;
	private Singleton() {}
    
    public static synchronized Singleton getInstance() {
        if(instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}

這種寫法能夠很好的在多線程中工作,不會出現併發安全問題,也實現了懶加載。但是效率很低,synchronized使得這裏並行變成串行。所以這種寫法一般不會被使用到。

3. 靜態內部類實現,推薦使用

public class Singleton {
    
    // === 靜態內部類 ===
    private static class SingletonHolder {
        private static final INSTANCE = new Singleton();
    }
    
    private Singleton() {}
    
    public static final Singleton getInstance() {
        return SingletonHolder.INSTANCE;
    }
}

這種方式同樣利用了class loader 的機制來保證初始化instance時只有一個線程,它跟上面提到的餓漢單例不同的是(很細微的差別):上面的餓漢單例是隻要Singleton類被裝載了,那麼instance就會被實例化(沒有達到lazy-loading效果)。而這種方式是Singleton類被裝載了,instance不一定被初始化。因爲SingletonHolder類沒有被主動使用,只有顯示調用getInstance()方法時,纔會顯示裝載SingletonHolder類,從而實例化instance。

4. 雙重校驗鎖實現,推薦使用

public class Singleton {
    // ===1:volatile修飾
    private volatile static Singleton singleton;
    
    private Singleton() {}
    
    public static Singleton getInstance() {
        // ===2:減少不必要同步,性能優化
        if (singleton == null) {
            // ===3:同步,線程安全
            synchronized(Singleton.class) {
                if (singleton == null) {
                    // ===4:創建singleton對象
                    singleton = new Singleton();
                }
            }
        }
        return singleton;
    }
}
  • 爲什麼要使用volatile修飾?

    雖然已經使用synchronized進行同步,但是第四步創建對象時,會有下面的僞代碼:

    memory=allocate();  // 1. 分配內存空間
    ctorInstance();     // 2. 初始化對象
    singleton=memory;   // 3. 設置singleton指向剛排序的內存空間
    

    當線程A在執行上面僞代碼時,2和3可能會發生重排序,因爲重排序並不影響運行結果,還可以提升性能,所以 JVM 是允許的。如果此時僞代碼發生重排序,步驟爲1 -> 3 -> 2,線程A執行到第3步時,線程B調用getInstance()方法,在判斷singleton == null時不爲null,則返回singleton。但此時singleton並沒有初始化完畢,線程B訪問的將是個還沒初始化完畢的對象。當聲明對象的引用爲volatile後,僞代碼的2、3的重排序在多線程中將被禁止!

5. 枚舉實現,推薦使用

public class Singleton {
    
    private Singleton(){}
    
    // === 延遲加載
    private enum EnumHolder {
        INSTANCE;
        private static Singleton instance = null;
        
        private Singleton getInstance() {
            instance = new Singleton();
        	return instance;
        }
    }
    
    public static Singleton getInstance() {
        return EnumHolder.INSTANCE.instance;
    }
}

三、問題注意

1. 不同類加載器加載

如果單例由不同的類加載器加載,那便有可能存在多個單例類的實例。假定不是遠端存取,例如一些Servlet容器對每個Servlet使用完全不同的類加載器,這樣的話如果有兩個Servlet訪問一個單例類,它們就都會有各自的實例。

可以用下面方式對這個問題進行修復:

private static Class getClass(String classname) throws ClassNotFoundException {
    ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
    if(classLoader == null) {
         classLoader = Singleton.class.getClassLoader();        
    }      
    return (classLoader.loadClass(classname));   
}

2. 序列化問題

如果Singleton實現了java.io.Serializable接口,那麼這個類的實例就可能被序列化和復原,不管怎樣,如果你序列化一個單例類的對象,接下來複原多個那個對象,那你就會有多個單例類的實例。

public class Singleton implements java.io.Serializable {     
    public static Singleton INSTANCE = new Singleton();     

    protected Singleton() {}
    
    private Object readResolve() {     
        return INSTANCE;     
    }
}  
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章