上一次为大家介绍了单例模式的基本概念和几种实现方式,没看过的小伙伴们可以点击下面链接:
漫画:什么是单例设计模式?
如果懒得去看也不要紧,让我们来简单回顾一下。
线程安全的懒汉型单例模式:
- public class Singleton {
- private Singleton() {} //私有构造函数
- private volatile static Singleton instance = null; //单例对象
- //静态工厂方法
- public static Singleton getInstance() {
- if (instance == null) { //双重检测机制
- synchronized(Singleton.class) { //同步锁
- if (instance == null) { //双重检测机制
- instance = new Singleton();
- }
- }
- }
- return instance;
- }
- }
该实现方式有几个值得注意的技术点:
这个实现方式虽然保证了线程安全,但仍然存在一些缺陷,如何写出更优雅的单例模式呢?让我们进入今天的主题。
用静态内部类实现单例模式:
- public class Singleton {
- private static class LazyHolder {
- private static final Singleton INSTANCE = new Singleton();
- }
- private Singleton() {}
- public static Singleton getInstance() {
- return LazyHolder.INSTANCE;
- }
- }
这里有几个需要注意的点:
1.从外部无法访问静态内部类LazyHolder,只有当调用Singleton.getInstance方法的时候,才能得到单例对象INSTANCE。
2.INSTANCE对象初始化的时机并不是在单例类Singleton被加载的时候,而是在调用getInstance方法,使得静态内部类LazyHolder被加载的时候。因此这种实现方式是利用classloader的加载机制来实现懒加载,并保证构建单例的线程安全。
如何利用反射打破单例模式的约束?其实很简单,我们来看下代码。
利用反射打破单例:
- //获得构造器
- Constructor con = Singleton.class.getDeclaredConstructor();
- //设置为可访问
- con.setAccessible(true);
- //构造两个不同的对象
- Singleton singleton1 = (Singleton) con.newInstance();
- Singleton singleton2 = (Singleton) con.newInstance();
- //验证是否是不同对象
- System.out.println(singleton1.equals(singleton2));
代码可以简单归纳为三个步骤:
第一步,获得单例类的构造器。
第二步,把构造器设置为可访问。
第三步,使用newInstance方法构造对象。
最后为了确认这两个对象是否真的是不同的对象,我们使用equals方法进行比较。毫无疑问,比较结果是false。
用枚举实现单例模式:
- public enum SingletonEnum {
- INSTANCE;
- }
让我们来做一个实验,仍然执行刚才的反射代码:
- //获得构造器
- Constructor con = SingletonEnum.class.getDeclaredConstructor();
- //设置为可访问
- con.setAccessible(true);
- //构造两个不同的对象
- SingletonEnum singleton1 = (SingletonEnum) con.newInstance();
- SingletonEnum singleton2 = (SingletonEnum) con.newInstance();
- //验证是否是不同对象
- System.out.println(singleton1.equals(singleton2));
执行结果如下:
Exception in thread "main" java.lang.NoSuchMethodException: com.heitian.ssm.utils.SingletonEnum.<init>()
at java.lang.Class.getConstructor0(Class.java:2892)
at java.lang.Class.getDeclaredConstructor(Class.java:2058)
at com.heitian.ssm.utils.SingletonTest.main(SingletonTest.java:22)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:606)
at com.intellij.rt.execution.application.AppMain.main(AppMain.java:134)
几点补充:
使用枚举实现的单例模式,不但可以防止利用反射强行构建单例对象,而且可以在枚举类对象被反序列化的时候,保证反序列的返回结果是同一对象。
对于其他方式实现的单例模式,如果既想要做到可序列化,又想要反序列化为同一对象,则必须实现readResolve方法。
公众号关注
来源: https://mp.weixin.qq.com/s?__biz=MzI0NDAzMzIyNQ==&mid=2654065385&idx=1&sn=aeeaf86da2604cd9e117e3895da10b14&chksm=f2a6814fc5d10859d8ee0a28b84e66d8b023a5e4d3b48024c6932b9ef3a74416c3550c9e4cbb#rd