什麼是單例模式?java
Intend:Ensure a class only has one instance, and provide a global point of access to it.設計模式
目標:保證一個類只有一個實例,並提供全局訪問點安全
--------(《設計模式:可複用面向對象軟件的基礎》bash
就運行機制來講,就是一個類,在運行過程當中只存在一分內存空間,外部的對象想使用它,都只會調用那部份內存。多線程
其目的有實現惟一控制訪問,節約資源,共享單例實例數據。ide
單例基礎實現有兩種思路,性能
1.Eager initialization:在加載類時構造;2.Lazy Initialization:在類使用時構造;3spa
1.Eager initialization適用於高頻率調用,其因爲預先建立好Singleton實例會在初始化時使用跟多時間,但在得到實例時無額外開銷線程
其典型代碼以下:設計
public class EagerInitSingleton { //構建實例 private static final EagerInitSingleton SINGLE_INSTANCE = new EagerInitSingleton(); //私有化構造器 private EagerInitSingleton(){} //得到實例 public static EagerInitSingleton getInstance(){ return SINGLE_INSTANCE; } }
換一種思路,因爲類內靜態塊也只在類加載時運行一次,因此也可用它來代替構造單例:
public class EagerInitSingleton { //構建實例 //private static final EagerInitSingleton instance = new EagerInitSingleton(); //此處不構造 private static StaticBlockSingleton instance; //使用靜態塊構造 static{ try{ instance = new StaticBlockSingleton(); }catch(Exception e){ throw new RuntimeException("Exception occured in creating singleton instance"); } } //私有化構造器 private EagerInitSingleton(){} //得到實例 public static EagerInitSingleton getInstance(){ return instance; } }
2.Lazy Initialization適用於低頻率調用,因爲只有使用時才構建Singleton實例,在調用時會有系列判斷過程因此會有額外開銷
2.1Lazy Initialization單線程版
其初步實現以下:
public class LazyInitSingleton { private static LazyInitSingleton SINGLE_INSTANCE = null; //私有化構造器 private LazyInitSingleton() {} //構造實例 public static LazyInitSingleton getInstance() { if (SINGLE_INSTANCE == null) { SINGLE_INSTANCE = new LazyInitSingleton();//判斷未構造後再構造 } return SINGLE_INSTANCE; } }
2.2Lazy Initialization多線程版
在多線程下,可能多個線程在較短期內一同調用 getInstance()方法,且判斷
SINGLE_INSTANCE == null
結果都爲true,則2.1Lazy Initialization單線程版 會構造多個實例,即單例模式失效
做爲修正
2.2.1synchronized關鍵字初版
可考慮使用synchronized關鍵字同步獲取方法
public class LazyInitSingleton { private static LazyInitSingleton SINGLE_INSTANCE = null; //私有化構造器 private LazyInitSingleton() {} //構造實例,加入synchronized關鍵字 public static synchronized LazyInitSingleton getInstance() { if (SINGLE_INSTANCE == null) { SINGLE_INSTANCE = new LazyInitSingleton();//判斷未構造後再構造 } return SINGLE_INSTANCE; } }
2.2.2synchronized關鍵字第二版(double checked locking 二次判斷鎖)
以上可實現線程安全,但因爲使用了synchronized關鍵字實現鎖定控制,getInstance()方法性能降低,形成瓶頸。分析到需求構建操做只限於未構建判斷後第一次調用getInstance()方法,即構建爲低頻操做,因此徹底能夠在判斷已經構建後直接返回,而不須要使用鎖,僅在判斷須要構建後才進行鎖定:
public class LazyInitSingleton { private static LazyInitSingleton SINGLE_INSTANCE = null; //私有化構造器 private LazyInitSingleton() {} //構造實例 public static synchronized LazyInitSingleton getInstance() { if (SINGLE_INSTANCE == null) { synchronized(LazyInitSingleton.class){ if (SINGLE_INSTANCE == null) { SINGLE_INSTANCE = new LazyInitSingleton();//判斷未構造後再構造 } } } return SINGLE_INSTANCE; } }
3利用靜態內部類實現懶加載
JVM僅在使用時加載靜態資源,當類加載時,靜態內部類不會加載,僅當靜態內部類在使用時會被加載,且實現順序初始化即加載是線程安全的,利用這一性質,咱們能夠實現懶加載
public class NestedSingleton { private NestedSingleton() {} //靜態內部類,只初始化一次 private static class SingletonClassHolder { static final NestedSingleton SINGLE_INSTANCE = new NestedSingleton(); } //調用靜態內部類方法獲得單例 public static NestedSingleton getInstance() { return SingletonClassHolder.SINGLE_INSTANCE; } }
4.使用Enum
因爲枚舉類是線程安全的,能夠直接使用枚舉建立單例。但考慮到枚舉沒法繼承,因此只在特定狀況下使用
public enum EnumSingleton { INSTANCE;
}
附1 利用反射機制破解單例(非Enum形式的單例,)
單例形式的類可被反射破解,從而使用單例失效,即將其Private構造器,經過反射形式暴露出來,並進行實例的構造
public static void main(String[] args) { NestedSingleton nestedSingleton = NestedSingleton.getInstance(); NestedSingleton nestedSingleton2 = null; try { //暴露構造器 Constructor[] constructors = nestedSingleton.getClass().getDeclaredConstructors(); Constructor constructor = constructors[1]; constructor.setAccessible(true); nestedSingleton2 = (NestedSingleton)constructor.newInstance(); } catch (Exception e ) { // TODO Auto-generated catch block e.printStackTrace(); } System.out.println(nestedSingleton.hashCode()); System.out.println(nestedSingleton2.hashCode()); } }
爲防止以上狀況,可在構造器中拋出異常,以阻止新的實例產生
public class NestedSingleton { private NestedSingleton() { synchronized (NestedSingleton.class) { //判斷是否已有實例 if(SingletonClassHolder.SINGLE_INSTANCE != null){ throw new RuntimeException("new another instance!"); } } } //靜態內部類,只初始化一次 private static class SingletonClassHolder { static final NestedSingleton SINGLE_INSTANCE = new NestedSingleton(); } //調用靜態內部類方法獲得單例 public static NestedSingleton getInstance() { return SingletonClassHolder.SINGLE_INSTANCE; } }
附2 序列化(Serialization)致使的單例失效
import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.ObjectInput; import java.io.ObjectInputStream; import java.io.ObjectOutput; import java.io.ObjectOutputStream; import java.io.OutputStream; public class SingletonSerializedTest { public static void main(String[] args) throws Exception { NestedSingleton nestedSingleton0 = NestedSingleton.getInstance(); ObjectOutput output =null; OutputStream outputStream = new FileOutputStream("serializedFile.ser"); output = new ObjectOutputStream(outputStream); output.writeObject(nestedSingleton0); output.close(); ObjectInput input = new ObjectInputStream(new FileInputStream("serializedFile.ser")); NestedSingleton nestedSingleton1 = (NestedSingleton) input.readObject(); input.close(); System.out.println("nestedSingleton0 hashCode="+nestedSingleton0.hashCode()); System.out.println("nestedSingleton1 hashCode="+nestedSingleton1.hashCode()); } }
輸出結果
nestedSingleton0 hashCode=865113938 nestedSingleton1 hashCode=2003749087
顯然,產生了兩個實例
若是要避免這個狀況,則須要利用ObjectInputStream.readObject()中的機制,它在調用readOrdinaryObject()後會判斷類中是否有ReadResolve()方法,若是有就採用類中的ReadResolve()新建實例
那麼以上單例類就能夠以下改造
import java.io.Serializable; public class NestedSingleton implements Serializable { /** * */ private static final long serialVersionUID = 3934012982375502226L; private NestedSingleton() { synchronized (NestedSingleton.class) { //判斷是否已有實例 if(SingletonClassHolder.SINGLE_INSTANCE != null){ throw new RuntimeException("new another instance!"); } } } //靜態內部類,只初始化一次 private static class SingletonClassHolder { static final NestedSingleton SINGLE_INSTANCE = new NestedSingleton(); } //調用靜態內部類方法獲得單例 public static NestedSingleton getInstance() { return SingletonClassHolder.SINGLE_INSTANCE; } public void printFn() { // TODO Auto-generated method stub System.out.print("fine"); } protected Object readResolve() { return getInstance(); } }
再次使用序列化反序列化過程驗證,獲得
nestedSingleton0 hashCode=865113938
nestedSingleton1 hashCode=865113938
這樣在序列化反序列化過程保證了單例的實現