搞清楚单例模式,其实很简单
“一个类有且仅有一个实例,并且自行实例化向整个系统提供。”
1. 构造方法定义成私有方法防止通过构造方法进行实例化对象
2. 提供一个统一的静态方法,调用时只能通过静态方法获取实例对象
1. 饿汉式
2. 懒汉式
3. 静态内部类
4. 枚举
我们已经提供了私有的构造方法和静态方法,定义了一个属性叫instance,并且指向new Singleton()。
不管能不能用得到,总之我们先把对象实例化。称之为饿汉式。
私有的构造方法不变,将instance指向null,在静态方法中,增加判断。
如果instance为空,进行实例化操作,否则,直接返回实例。这种方式实现了延迟加载,在被调用时,进行实例化,称为懒汉式。
弊端:多线程环境下,无法保证唯一实例。
当类被加载的时候,内部类并不会被初始化,调用getInstance()方法时,才会被加载,然后进行实例化。并且,实例的创建是在类加载时完成的,所以并不需要使用同步关键字。
枚举类是线程安全的,不需要使用同步代码块来实现,更能保证不被反射和反序列化攻击。
尝试用反射破坏枚举单例
因为Java的枚举类都隐式的继承自Enum抽象类,Enum抽象类本身并没有无参的构造方法。只有一个Enum(String name, int ordinal)的构造方法。所以会报NoSuchMethodException,我们在来看一下Constructor的newInstance()方法的源码
可以看到如果类是ENUM修饰,则抛出异常,所以JDK反射机制从内部就已经禁止了用反射创建枚举实例
尝试用序列化反序列化破坏枚举单例
结果:序列化和反序列化是同一个对象
看下源码,在ObjectInputStream类中会判断如果是枚举类型的调用readEnum()方法
在readEnum()方法中,通过描述符获取枚举单例的类型,在获取单例的名字,然后在调用Enum.valueOf()方法,根据类型和名字直接获取到单例对象
总结:枚举是目前比较好的一种实现单例的方式