單例模式是一種設(shè)計模式,是在整個運行過程中只需要產(chǎn)生一個實例实柠。那么怎樣去創(chuàng)建呢,以下提供了幾種方案善涨。
一窒盐、創(chuàng)建單例對象
懶漢式
public class TestSingleton {
// 構(gòu)造方法私有化
private TestSingleton(){}
// 聲明實例
private static TestSingleton singleton;
// 提供外部調(diào)用方法草则,生成并獲取實例
public static TestSingleton getInstance() {
if(singleton == null) {
singleton = new TestSingleton();
}
return singleton;
}
}
此方案是以時間換空間,啟動時并不會執(zhí)行任何操作蟹漓,只有被調(diào)用時炕横,采取實例化對象。不過這種方法在多線程下不安全葡粒,因為兩個線程如果同時調(diào)用時份殿,會同時通過非空驗證的驗證,造成創(chuàng)建兩個對象的后果嗽交,有悖設(shè)計初衷卿嘲。
針對多線程問題,應(yīng)該加入雙重非空判斷:
public class TestSingleton {
// 構(gòu)造方法私有化
private TestSingleton(){}
// 聲明實例
private static volatile TestSingleton singleton;
// 提供外部調(diào)用方法夫壁,生成并獲取實例
public static TestSingleton getInstance() {
if(singleton == null) {
synchronized (TestSingleton.class) {
if(singleton == null) {
singleton = new TestSingleton();
}
}
}
return singleton;
}
}
餓漢式
public class TestSingleton {
// 構(gòu)造方法私有化
private TestSingleton(){}
// 聲明并生成實例
private static TestSingleton singleton = new TestSingleton();
// 提供外部調(diào)用方法拾枣,獲取實例
public static TestSingleton getInstance() {
return singleton;
}
}
以空間換時間,類一加載時盒让,就對其進行實例化梅肤,后面調(diào)用時直接提供對象實例。
靜態(tài)內(nèi)部類實現(xiàn)懶加載
public class TestSingleton {
// 構(gòu)造方法私有化
private TestSingleton(){}
private static class TestSingletonFactory{
private static TestSingleton singleton = new TestSingleton();
}
// 提供外部調(diào)用方法邑茄,獲取實例
public static TestSingleton getInstance() {
return TestSingletonFactory.singleton;
}
}
當(dāng)getInstance方法被調(diào)用時姨蝴,才會初始化靜態(tài)內(nèi)部類TestSingletonFactory的靜態(tài)變量singleton。此處由JVM來保障線程安全肺缕。
二似扔、破壞單例
實現(xiàn)單例后,按照預(yù)期結(jié)果應(yīng)該所有對象都是同一個對象搓谆。但是以下有幾種情況可以破壞單例的性質(zhì)炒辉。
首先讓單例類實現(xiàn)Serializable, Cloneable接口,以便實驗泉手。
public class TestSingleton implements Serializable, Cloneable{
private static final long serialVersionUID = 1L;
// 構(gòu)造方法私有化
private TestSingleton(){}
private static class TestSingletonFactory{
private static TestSingleton singleton = new TestSingleton();
}
// 提供外部調(diào)用方法黔寇,獲取實例
public static TestSingleton getInstance() {
return TestSingletonFactory.singleton;
}
}
- 序列化
// 獲取實例
TestSingleton originSingleton = TestSingleton.getInstance();
// 寫出對象
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(originSingleton);
// 寫入對象
ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bis);
TestSingleton serializeSingleton = (TestSingleton) ois.readObject();
// 判斷兩個對象是否相等
System.out.println(originSingleton == serializeSingleton); // false
- 反射
// 反射
Class<TestSingleton> clazz = TestSingleton.class;
// 獲取無參構(gòu)造函數(shù)
Constructor<TestSingleton> constructor = clazz.getDeclaredConstructor();
// 將私有設(shè)置為可見
constructor.setAccessible(true);
// 用構(gòu)造器生成實例
TestSingleton instance = constructor.newInstance();
// 判斷兩個對象是否相等
System.out.println(originSingleton == instance); // false
- 克隆
// 克隆
TestSingleton clone = (TestSingleton) originSingleton.clone();
System.out.println(originSingleton == clone); // false
三、修復(fù)破壞
對于這種預(yù)料之外的結(jié)果斩萌,我們應(yīng)該怎樣去控制呢缝裤?
- 序列化
添加readResolve方法,返回Object颊郎。
- 反射
添加全局可見變量憋飞,如果再次調(diào)用構(gòu)造方法生成實例時,拋出運行時錯誤姆吭。
- 克隆
重寫clone方法榛做,直接返回單例對象。
public class TestSingleton implements Serializable, Cloneable{
private static final long serialVersionUID = 1L;
private static volatile boolean isCreated = false;//默認是第一次創(chuàng)建
// 構(gòu)造方法私有化
private TestSingleton(){
if(isCreated) {
throw new RuntimeException("實例已經(jīng)被創(chuàng)建");
}
isCreated = true;
}
private static class TestSingletonFactory{
private static TestSingleton singleton = new TestSingleton();
}
// 提供外部調(diào)用方法,獲取實例
public static TestSingleton getInstance() {
return TestSingletonFactory.singleton;
}
@Override
protected Object clone() throws CloneNotSupportedException {
return getInstance();
}
/**
* 防止序列化破環(huán)
* @return
*/
private Object readResolve() {
return getInstance();
}
}