单例模式
我们知道,单例模式的实现,主要由三个步骤组成,即:
- 声明一个静态私有的成员变量;
- 私有化构造函数;
- 声明一个静态共有的函数,函数返回该对象的实例。
一个典型的单例类定义如下:
public class Sigleton {
private static final Sigleton INSTANCE = new Sigleton();
private Sigleton() {}
public static Sigleton getInstance() {
return INSTANCE;
}
}
但是,在java中,享有特权的客户端可以通过反射机制 AccessibleObject.setAccessible
来调用私有构造器,所以为了防止出现这种问题,可以将私有构造器进行改造,在调用私有构造器的时候抛出异常即可,如下:
private Sigleton() {
if (INSTANCE != null) {
throw new UnsupportedOperationException("instance already exist!");
}
}
但即使如此,仍然有一种情况会出现多个实例:在序列化这个对象的实例后,再对该实例进行反序列化,会得到该对象的一个新的实例。
下面做一下测试:
1. 将Sigleton类实现Serializable接口
public class Sigleton implements Serializable {
......
}
2. 进行序列化反序列化测试
public class SerializableTest {
public static void main(String[] args) throws Exception{
// 序列化Singleton实例
serializable(Sigleton.getInstance(), "test");
// 反序列化该实例
Sigleton singleton = deserializable("test");
// 检查序列化前和序列化后的实例是否相等
if (singleton != Sigleton.getInstance()) {
System.out.println("singleton: " + singleton.toString());
System.out.println("Singleton.getInstance: " + Sigleton.getInstance());
System.out.println("two instances are different\n");
} else {
System.out.println("two instances are the same\n");
}
}
//序列化
private static void serializable(Sigleton singleton, String filename) throws IOException {
FileOutputStream fos = new FileOutputStream(filename);
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(singleton);
oos.flush();
}
//反序列化
@SuppressWarnings("unchecked")
private static <T> T deserializable(String filename) throws IOException,ClassNotFoundException {
FileInputStream fis = new FileInputStream(filename);
ObjectInputStream ois = new ObjectInputStream(fis);
return (T) ois.readObject();
}
}
运行该程序,得到如下结果:
singleton: com.effective.java.sigleton.Sigleton@1d81eb93
Singleton.getInstance: com.effective.java.sigleton.Sigleton@5e481248
two instances are different
可以看出序列化前和序列化后得到的两个实例是不一样的。那么如何解决上述问题呢,一共有两种解决方式。
方法一:添加readResolve函数
在Sigleton单例类中添加readResolve函数
private Object readResolve() {
return INSTANCE;
}
接下来再次运行SerializableTest
程序,得到结果:
two instances are the same
方法二:利用枚举来强化Singlton
public enum SingletonEnum {
/**
* 实例
*/
INSTANCE;
private String field;
public String getField() {
return field;
}
public void setField(String field) {
this.field = field;
}
}
// 调用方法
SingletonEnum.INSTANCE.setField("123");
System.out.println(SingletonEnum.INSTANCE.getField());
这种方法在功能上与共有域方法相近,但是更加简洁,无偿的提供了序列化机制,绝对防止多次实例化,即使在面对复杂的序列化或者反射调用时。虽然这种方法还没有被广泛使用,但是单元素的枚举类型已经成为实现Singleton
的最佳方法。
github完整代码
https://github.com/HorizonLiu/effective.java.learning