前言
上文我们了解常见的单例模式的创建方式,但还有没有其他的方式呢?在日常开发中推荐使用哪种呢?本文将带你深入了解其他的单例创建方式,以及单例模式的破坏。
单例模式之静态内部类
静态内部类的方式其实很简单,它就是根据静态内部类的外部类的加载不影响内部类特性,同时解决了按需加载、线程安全的问题,代码也不复杂,总结起来就是:
- 在静态内部类里创建单例,在装载该内部类时才会去创建单例。
- 类是由 JVM加载,而JVM只会加载1遍,保证只有1个单例,线程安全。
public class Singleton {
/**
* 处理器
*/
private static class SingletonHandler {
private static Singleton instance = new Singleton();
}
/**
* 私有构造方法,无法通过new Singleton()方式创建
*/
private Singleton() {
}
/**
* 通过处理器获取实例
*/
public static Singleton getInstance() {
return SingletonHandler.instance;
}
}
单例模式之枚举
这种方式好在于构造方法会被自动调用,利用这一特性也可以实现单例,默认枚举实例的创建本就是是线程安全的,即使反序列化也不会生成新的实例,任何情况下都是一个单例,满足单例模式所需的:单例创建、线程安全、代码复杂度不高,也是推荐使用的方式。
注意:暴力反射对枚举方式无效哦。
public enum Singleton {
INSTANCE;
private Object data;
public Object getData() {
return data;
}
public void setData(Object data) {
this.data = data;
}
public static Singleton getInstance() {
return INSTANCE;
}
}
反射对于单例的破坏
先看回顾一下Java反射的概念,Java反射是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法。对于任意一个对象,都能够调用它的任意方法和属性。这种动态获取信息、动态调用对象方法的功能就是Java的反射机制。
注意:从上述来看,这种技术过于NiuBi了,其实不仅如此,它还可以可以通过setAccessible方法(通常被称为暴力反射)来修改构造器、字段、方法的可见性。也就是说他可以对类中的私有成员进行操作,那么就破坏了我们的全局访问节点了。
public class Reflect {
public static void main(String[] args) throws Exception {
// 首先要获取到该类的Class 对象。
Class<Singleton> clazz = Singleton.class;
// getDeclaredConstructor: 不受权限控制的获取类的构造器
Constructor c = clazz.getDeclaredConstructor(null);
// 设置为true,就可以对类中的私有成员进行操作了
c.setAccessible(true);
Object instance1 = c.newInstance();
Object instance2 = c.newInstance();
// false
System.out.println(instance1 == instance2);
}
}
public class Singleton {
private static class SingletonHandler {
private static Singleton instance = new Singleton();
}
private Singleton() {
}
public static Singleton getInstance() {
return SingletonHandler.instance;
}
}
那么如何解决呢?其实也很简单,只需要在单例类的构造方法中,添加判断即可。
public class Singleton {
private static class SingletonHandler {
private static Singleton instance = new Singleton();
}
private Singleton() {
if(SingletonHandler.instance != null) {
throw new RuntimeException("警告!禁止非法访问!");
}
}
public static Singleton getInstance() {
return SingletonHandler.instance;
}
}
为什么枚举类可以阻止反射的破坏?
Java规范字规定,每个枚举类型及其定义的枚举变量在JVM中都是唯一的,因此在枚举类型的序列化和反序列化上,Java做了特殊的规定。在序列化的时候Java仅仅是将枚举对象的name属性输到结果中,反序列化的时候则是通过java.lang.Enum的valueOf()方法来根据名字查找枚举对象。
在上文中序列化的时候只将这个INSTANCE名称输出,反序列化的时候再通过这个名称,查找对应的枚举类型,因此反序列化后的实例也会和之前被序列化的对象实例相同。
而枚举类中是没有空参构造方法的,反射方法中不予许使用反射创建枚举对象。
总结
由上文所述,静态内部类,利用 Java 的静态内部类来实现单例。这种实现方式,既支持延迟加载,也支持高并发,实现起来也比双重检测简单。而枚举方式是最简单的实现方式,基于枚举类型的单例实现。这种实现方式通过Java的枚举类型它本身的特性,保证了实例创建的线程安全性和实例的唯一性(同时阻止了反射和序列化对单例的破坏),但是破坏单例的不只有反射,想想如果是序列化的方式,有没有问题?