單例模式
單例模式是指在記憶體中隻會建立且僅建立一次對象的設計模式。在程式中多次使用同一個對象且作用相同時,為了防止頻繁地建立對象使得記憶體飙升,單例模式可以讓程式僅在記憶體中建立一個對象,讓所有需要調用的地方都共享這一單例對象。
單例模式的類型
單例模式有兩種類型:
- 懶漢式:在真正需要使用對象時才去建立該單例類對象
- 餓漢式:在類加載時已經建立好該單例對象,等待被程式使用
餓漢式:
- 線程安全
- 不能實作延遲加載(浪費空間)
public class Singleton {
private static Singleton instance = new Singleton();
private Singleton() {
}
public static Singleton getInstance() {
return instance;
}
}
不管程式是否需要這個對象的執行個體,總是在類加載的時候就先建立好執行個體,了解起來就像不管一個人想不想吃東西都把吃的先買好,如同餓怕了一樣。
懶漢式:
最初懶漢式0
- 延遲加載
- 非線程安全
public class Singleton {
private static Singleton instance = null;
private Singleton() {
}
public static Singleton getInstance() {
if (null == instance) {
instance = new Singleton();
}
return instance;
}
}
如果一個對象使用頻率不高,占用記憶體還特别大,明顯就不合适用餓漢式了,這時就需要一種懶加載的思想,當程式需要這個執行個體的時候才去建立對象,就如同一個人懶的餓到不行了才去吃東西。
懶漢式1
- 延遲加載
- 線程安全 - 但每次擷取都需要加鎖,性能低下
public class Singleton {
private static Singleton instance = null;
private Singleton() {
}
public static synchronized Singleton getInstance() {
if (instance == null)
instance = new Singleton()
}
return instance;
}
懶漢式2(雙重檢查加鎖):
- 延遲加載
- 線程安全 - 效率相比1提升,建立後不必每次加鎖
public class Singleton {
private volatile static Singleton instance = null;
private Singleton() {
}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
使用靜态内部類實作單例模式
- 延遲加載
- 線程安全(利用類加載機制,保證線程安全) : 雙親委派機制保證一個類隻加載一次,進而保證線程安全
public class Singleton {
private static class SingletonHoler {
/**
* 靜态初始化器,由JVM來保證線程安全
*/
private static Singleton instance = new Singleton();
}
private Singleton() {
}
public static Singleton getInstance() {
return SingletonHoler.instance;
}
}
使用枚舉來實作單例
public enum Singleton {
uniqueInstance;// 定義一個枚舉的元素,它 就代表了Singleton的一個執行個體
public void singletonOperation() {
// 功能處理
System.err.println("功能處理");
}
}
破壞單例模式的三種方式
- 反射
- 序列化
- 克隆
解決方案如下:
1、防止反射
定義一個全局變量,當第二次建立的時候抛出異常
private static volatile boolean isCreate = false;//預設是第一次建立
/**
* 1.構造方法私有化,外部不能new
*/
private Singleton() {
if(isCreate) {
throw new RuntimeException("已然被執行個體化一次,不能在執行個體化");
}
isCreate = true;
}
2、防止克隆破壞
重寫clone(),直接傳回單例對象
@Override
protected Object clone() throws CloneNotSupportedException {
return instance;
}
3、防止序列化破壞
從序列化中恢複一個單例對象會破壞單例模式,解決方法是添加
readResolve()
:
原理:
- 反序列化時,首先擷取序列化的類 : desc( 可了解為單例類的class類,但它和JVM加載到記憶體中的單例class類有不同)因為如果desc就是我們的單例class類,那是不允許再執行個體化的。而desc類卻可以執行個體化。
- 判斷對象是否能執行個體化。可以則進行執行個體化,至此單例類進行了第一次執行個體化,對象名為obj
- 第一次執行個體化完成後,通過反射尋找該單例類中的readResolve()方法,沒有則直接傳回obj對象。(這就是直接反序列化破壞單例模式的原因)
- 有定義readResolve()方法,desc通過invokeReadResolve(Object obj)方法調用readResolve()方法擷取單例對象instance,将他指派給rep,如果單例對象之前已經被執行個體化過,那麼rep就會指向之前執行個體化的單例對象。如果我們之前沒有執行個體化單例對象,則rep會指向null。
- rep與obj進行比較,由于obj是反射擷取的對象,當然與rep不等,于是将rep的值instance指派給obj,将obj傳回,傳回對象instance也就保證了單例。
簡而言之就是,當我們通過反序列化readObject()方法擷取對象時會去尋找readResolve()方法,如果該方法不存在則直接傳回新對象,如果該方法存在則按該方法的内容傳回對象,以確定如果我們之前執行個體化了單例對象,就傳回該對象。如果我們之前沒有執行個體化單例對象,則會傳回null。
參考文章
/**
* 防止序列化破環
* @return
*/
private Object readResolve() {
return instance;
}
最終版
private static class Singleton implements Serializable,Cloneable{
private static volatile boolean isCreate = false;//預設是第一次建立
/**
* 1.構造方法私有化,外部不能new
*/
private Singleton() {
if(isCreate) {
throw new RuntimeException("已然被執行個體化一次,不能在執行個體化");
}
isCreate = true;
}
//2.本類内部建立對象執行個體
private static volatile Singleton instance;
//3.提供一個公有的靜态方法,傳回執行個體對象
public static Singleton getInstance() {
if(instance == null) {
synchronized (Singleton.class) {
if(instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
@Override
protected Object clone() throws CloneNotSupportedException {
return instance;
}
/**
* 防止序列化破環
* @return
*/
private Object readResolve() {
return instance;
}
}