通常情況下怀各,我們寫單例模式的時(shí)候無非就是三個(gè)步驟:構(gòu)造器私有化听系,聲明私有靜態(tài)變量舍悯,提供靜態(tài)獲取實(shí)例的方法航棱。簡(jiǎn)單說就是以下這種方式:
class SingletonA {
private static SingletonA instence = new SingletonA();
private SingletonA() {
}
public static SingletonA getInstance() {
return instence;
}
}
這是最基本的單例模式的寫法睡雇,考慮到線程安全的問題,會(huì)用synchronized 關(guān)鍵字修飾getInstance()方法饮醇,另外還有餓漢式它抱、懶漢式、靜態(tài)內(nèi)部類驳阎、雙重校驗(yàn)鎖的寫法抗愁。
但是這種寫法存在缺陷,可以利用反射的方式來實(shí)例化多個(gè)不同的實(shí)例呵晚,如下所示:
private static void testReflex() {
try {
SingletonA s1 = SingletonA.getInstance();
Class<SingletonA> cls = SingletonA.class;
Constructor<SingletonA> constructor = cls
.getDeclaredConstructor(new Class[] {});
constructor.setAccessible(true);
SingletonA s2 = constructor.newInstance(new Object[] {});
System.out.println(s1 == s2);//false
} catch (Exception e) {
e.printStackTrace();
}
}
這種情況下蜘腌,就會(huì)出現(xiàn)多個(gè)不同的實(shí)例,從而導(dǎo)致一些亂七八糟的結(jié)果饵隙。
還有一種情況就是在序列化和反序列換的時(shí)候也會(huì)出現(xiàn)多個(gè)不同的實(shí)例撮珠,如下:
class SingletonB implements Serializable {
private static SingletonB instence = new SingletonB();
private SingletonB() {
}
public static SingletonB getInstance() {
return instence;
}
}
private static void testSingletonB() {
File file = new File("singleton");
ObjectOutputStream oos = null;
ObjectInputStream ois = null;
try {
oos = new ObjectOutputStream(new FileOutputStream(file));
SingletonB SingletonB1 = SingletonB.getInstance();
oos.writeObject(SingletonB1);
oos.close();
ois = new ObjectInputStream(new FileInputStream(file));
SingletonB SingletonB2 = (SingletonB) ois.readObject();
System.out.println(SingletonB1 == SingletonB2);//false
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} finally {
if (oos != null) {
try {
oos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (ois != null) {
try {
ois.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
這種情況也會(huì)出現(xiàn)有多個(gè)實(shí)例的問題,這個(gè)問題可以在類中添加readResolve()方法來避免金矛,即:
class SingletonB implements Serializable {
private static SingletonB instence = new SingletonB();
private SingletonB() {
}
public static SingletonB getInstance() {
return instence;
}
// 不添加該方法則會(huì)出現(xiàn) 反序列化時(shí)出現(xiàn)多個(gè)實(shí)例的問題
public Object readResolve() {
return instence;
}
}
這樣在反序列化的時(shí)候就不會(huì)出現(xiàn)多個(gè)實(shí)例芯急。
使用單元素的枚舉實(shí)現(xiàn)單例模式
一個(gè)最簡(jiǎn)單的POJO類,如下:
enum SingletonC implements Serializable {
INSTANCE;
private String field;
public String getField() {
return field;
}
public void setField(String field) {
this.field = field;
}
}
測(cè)試方法:
private static void testEnum() {
File file = new File("singletonEnum");
ObjectOutputStream oos = null;
ObjectInputStream ois = null;
try {
oos = new ObjectOutputStream(new FileOutputStream(file));
SingletonC singleton = SingletonC.INSTANCE;
oos.writeObject(SingletonC.INSTANCE);
oos.close();
ois = new ObjectInputStream(new FileInputStream(file));
SingletonC singleton2 = (SingletonC) ois.readObject();
System.out.println(singleton == singleton2);//true
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} finally {
if (oos != null) {
try {
oos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (ois != null) {
try {
ois.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
這種實(shí)現(xiàn)單例模式的方式是在 1.5之后才出現(xiàn)的
這種方法在功能上與公有域方法相近驶俊,但是它更加簡(jiǎn)潔娶耍,無償提供了序列化機(jī)制,絕對(duì)防止多次實(shí)例化饼酿,即使是在面對(duì)復(fù)雜序列化或者反射攻擊的時(shí)候榕酒。雖然這種方法還沒有廣泛采用,但是單元素的枚舉類型已經(jīng)成為實(shí)現(xiàn)Singleton的最佳方法故俐。 —-《Effective Java 中文版 第二版》