單例模式是一種常用的設計模式,其定義是單例對象的類只能允許一個實例存在。下面來看看幾種常見的單例模式的寫法,以及如何保證線程安全的實現。
1、餓漢式(線程安全)
這種寫法比較簡單,就是在類裝載的時候就完成實例化。避免了線程同步問題。但是在類裝載的時候就完成實例化,沒有達到懶加載的效果。如果從始至終從未使用過這個實例,則會造成內存的浪費。
public class Singleton {
private static final Singleton instance = new Singleton();
private Singleton(){}
public static Singleton getInstance(){
return instance ;
}
}
2、懶漢式(線程安全)
這種方式效率太低了,每個線程在想獲得類的實例的時候,執行getInstance()方法都要進行同步。而其實這個方法只執行一次實例化代碼就夠了,后面的想獲得該類實例,直接return就行了。也就是我們之前提到的同步的粒度太粗,synchronized 同步代碼應該是越細越好。
public class Singleton {
private static Singleton singleton;
private Singleton() {}
public static synchronized Singleton getInstance() {
if (singleton == null) {
singleton = new Singleton();
}
return singleton;
}
}
3、懶漢式(線程安全)的細粒度優化(雙重鎖機制)
對上一種模式進行優化,這里判斷了兩次是否為 null 是因為在並發環境中當線程一執行了第一個判斷的時候是為null,可此刻另外一個線程正好執行完初始化操作,在釋放鎖以后該線程並不知道已經初始化,如果此刻進入代碼塊不進行再次判斷會再初始化一次,這就違背了單例模式的初衷了。
public class Singleton {
private static Singleton instance;
private Singleton (){}
public static Singleton getInstance(){
if (instance == null){
synchronized(Singleton.class){
if (instance == null)
instance = new Singleton();
}
}
return instance;
}
}
4、靜態內部類(懶加載,線程安全)
這種方式跟餓漢式方式采用的機制類似,但又有不同。兩者都是采用了類裝載的機制來保證初始化實例時只有一個線程。不同的地方在餓漢式方式是只要Singleton類被裝載就會實例化,沒有懶加載的作用,而靜態內部類方式在Singleton類被裝載時並不會立即實例化,而是在需要實例化時,調用getInstance方法,才會裝載SingletonInstance類,從而完成Singleton的實例化。類的靜態屬性只會在第一次加載類的時候初始化,所以在這里,JVM幫助我們保證了線程的安全性,在類進行初始化時,別的線程是無法進入的。避免了線程不安全,延遲加載,效率高。
public class Singleton {
private Singleton() {}
//內部類在外部類調用的時候才會被初始化
// 內部類一定要在方法調用之前初始化
private static class SingletonInstance {
private static final Singleton instance = new Singleton();
}
// static 使單例空間共享
// final使得方法不能被重寫重載
public static final Singleton getInstance() {
return SingletonInstance.instance;
}
}
這里可以在私有的構造方法中進行一個雙重鎖的判斷,定義一個 flag來判斷該構造是否被重復調用,來防止反射的侵入。
除此之外還可以使用枚舉類的方式來實現單例模式。由於實際工作中並未發現有人這么做,這里就不演示了。
