单例模式的创建这里就不再赘述,先写下一般单例模式的创建.
import java.io.Serializable;
/**
* @author YoonaLt
* 惰性加载,线程安全的单例模式
*/
public class Singleton implements Serializable {
/**
* 使用 volatile 关键字禁止指令重排
*/
private static volatile Singleton singleton;
/**
* 构造器私有化,禁止外部创建
*/
private Singleton() {
}
/**
* 实现惰性加载
*
* @return Singleton
*/
public static Singleton getInstance() {
if (singleton == null) {
// 加锁
synchronized (Singleton.class) {
if (singleton == null) {
return singleton = new Singleton();
}
}
}
return singleton;
}
}
正常使用时,通过类对外提供的 getInstance() 方法获取类对象 :
Singleton s1 = Singleton.getInstance();
Singleton s2 = Singleton.getInstance();
获取到的对象为同一对象,单例模式成功.
通过反序列化的方式来获取:
// 通过反序列化获取单例模式对象
String path = "my/SingletonOut";
Singleton original = Singleton.getInstance();
// 把对象 original 写入文件
try (ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream(path))) {
objectOutputStream.writeObject(original);
// 把文件上的对象读出来
ObjectInputStream result = new ObjectInputStream(new FileInputStream(path));
Singleton resultSingleton = (Singleton) result.readObject();
// 反序列化出来的对象,和原对象,不是同一个对象。
result.close();
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
此时获取到的两个对象 original 与 resultSingleton 为不同的两个对象,单例模式被破坏.
解决反序列化破坏单例的办法比较简单,只需要在单例类中添加方法
private Object readResolve() throws ObjectStreamException {
return singleton;
}
就可以解决反序列化破坏单例模式的问题.
我们在使用反射来获取这个单例模式的对象
// 获取 Singleton 的 Class 对象
Class singletonClass = Class.forName("com.test.yoonalt.Singleton");
// 获取无参构造器
Constructor constructor = singletonClass.getDeclaredConstructor();
// 构造器访问设置为可见
constructor.setAccessible(true);
// 调用获取到的构造器对象的方法获取对应的实例
Singleton one = (Singleton) constructor.newInstance();
Singleton two = (Singleton) constructor.newInstance();
获取到的对象 one 与 two 并不是同一对象,单例模式被破坏.
网上有一部分防止反射破坏到单例模式的解决办法是在私有化的构造器内添加一个简单的判断:
private Singleton() {
if (singleton != null) {
throw new RuntimeException("不能重复创建对象");
}
}
这种方式并没有用,如果直接通过反射来创建对象,那么单例类的类属性 singleton 总为 null,所以无论创建多少对象,永远不会进入构造器内的 if 判断中.
再次通过 静态内部类 的方式重新定义单例类
public class Singleton implements Serializable {
private static class SingletonStaticClass {
private static final Singleton singleton = new Singleton();
}
private Singleton() {
synchronized (Singleton.class) {
if (SingletonStaticClass.singleton != null) {
throw new RuntimeException("不能重复创建对象");
}
}
}
private Object readResolve() throws ObjectStreamException {
return SingletonStaticClass.singleton;
}
public static Singleton getInstance() {
return SingletonStaticClass.singleton;
}
}
如果想要通过反射获取其静态内部类 SingletonStaticClass 并给其属性 singleton 赋值为 null 时,就会抛出异常 IllegalAccessException ,阻止将其值设置为 null.代码如下:
// 获取单例类的 Class
Class singletonClass = Singleton.class;
// 获取所有内部类
Class[] classes = singletonClass.getDeclaredClasses();
Class inside = classes[0];
// 获取内部类的属性
Field field = inside.getDeclaredField("singleton");
// 设置属性权限可修改
field.setAccessible(true);
String fileName = field.getName();
Object filedValue = field.get(inside);
System.out.println("获取到的属性名 name = " + fileName + ", 值为 value = " + filedValue);
// 将属性值修改为 null,会抛出 IllegalAccessException
field.set(inside, null);
使用静态内部类的方式可以防止被反射破坏单例.
最建议的一种方法是使用 枚举 来实现单例,这种方式天然的防止通过反射来破坏单例.只需要 jdk1.5 及以上就可.