通常情况下,我们写单例模式的时候无非就是三个步骤:构造器私有化,声明私有静态变量,提供静态获取实例的方法。简单说就是以下这种方式:
class SingletonA {
private static SingletonA instence = new SingletonA();
private SingletonA() {
}
public static SingletonA getInstance() {
return instence;
}
}
这是最基本的单例模式的写法,考虑到线程安全的问题,会用synchronized 关键字修饰getInstance()方法,另外还有饿汉式、懒汉式、静态内部类、双重校验锁的写法。
但是这种写法存在缺陷,可以利用反射的方式来实例化多个不同的实例,如下所示:
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();
}
}
这种情况下,就会出现多个不同的实例,从而导致一些乱七八糟的结果。
还有一种情况就是在序列化和反序列换的时候也会出现多个不同的实例,如下:
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();
}
}
}
}
这种情况也会出现有多个实例的问题,这个问题可以在类中添加readResolve()方法来避免,即:
class SingletonB implements Serializable {
private static SingletonB instence = new SingletonB();
private SingletonB() {
}
public static SingletonB getInstance() {
return instence;
}
// 不添加该方法则会出现 反序列化时出现多个实例的问题
public Object readResolve() {
return instence;
}
}
这样在反序列化的时候就不会出现多个实例。
使用单元素的枚举实现单例模式
一个最简单的POJO类,如下:
enum SingletonC implements Serializable {
INSTANCE;
private String field;
public String getField() {
return field;
}
public void setField(String field) {
this.field = field;
}
}
测试方法:
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();
}
}
}
}
这种实现单例模式的方式是在 1.5之后才出现的
这种方法在功能上与公有域方法相近,但是它更加简洁,无偿提供了序列化机制,绝对防止多次实例化,即使是在面对复杂序列化或者反射攻击的时候。虽然这种方法还没有广泛采用,但是单元素的枚举类型已经成为实现Singleton的最佳方法。 —-《Effective Java 中文版 第二版》