欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 科技 > 能源 > 单例模式的几种写法及破坏

单例模式的几种写法及破坏

2024/10/24 17:23:14 来源:https://blog.csdn.net/m0_63530048/article/details/141223498  浏览:    关键词:单例模式的几种写法及破坏

1)饿汉式

对内存要求不高的时候可以使用

public class Singleton{private static final Singleton singleton = new Singleton();private Singleton(){}public static Singleton getInstance() {return singleton;}
}

(2)懒汉式

  • 指令重排:

创建一个对象,在JVM中会经过三步:

(1)为singleton分配内存空间

(2)初始化singleton对象

(3)将singleton指向分配好的内存空间

指令重排序是指:JVM在保证最终结果正确的情况下,可以不按照程序编码的顺序执行语句,尽可能提高程序的性能。在这三步中,第2、3步有可能会发生指令重排现象,创建对象的顺序变为1-3-2,会导致多个线程获取对象时,有可能线程A创建对象的过程中,执行了1、3步骤,线程B判断singleton已经不为空,获取到未初始化的singleton对象,就会报NPE异常。

public class Singleton {//volatile防止指令重排并使其变更在不同线程中可见private static volatile Singleton singleton;//构造函数私有化,防止外界构造对象private Singleton(){}//静态方法,可以直接用类.方法调用public static Singleton getInstance() {//双检法,避免先要获取锁才能进行检查,提高并发性能if (singleton == null) {synchronized(Singleton.class) {if (singleton == null) {singleton = new Singleton();}}}return singleton;}
}

(3)单例模式的破坏

饿汉式和懒汉式都可以破坏

反射

可以通过反射强制访问被私有的构造器,去创建对象

public static void main(String[] args) {// 获取类的显式构造器Constructor<Singleton> construct = Singleton.class.getDeclaredConstructor();// 可访问私有构造器construct.setAccessible(true); // 利用反射构造新对象Singleton obj1 = construct.newInstance(); // 通过正常方式获取单例对象Singleton obj2 = Singleton.getInstance(); System.out.println(obj1 == obj2); // false
}

序列化和反序列化

当你将一个单例对象写入文件(或任何其他输出流),然后从该文件读取时,Java 的序列化机制会创建一个新的对象实例。

为了防止这种行为破坏单例模式,你需要重写 `readResolve()` 方法,在 `readResolve()` 方法中返回单例实例的引用,这样即使经过序列化和反序列化,你仍然只会有一个单例实例。

public static void main(String[] args) {// 创建输出流ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("Singleton.file"));// 将单例对象写到文件中oos.writeObject(Singleton.getInstance());// 从文件中读取单例对象File file = new File("Singleton.file");ObjectInputStream ois =  new ObjectInputStream(new FileInputStream(file));Singleton newInstance = (Singleton) ois.readObject();// 判断是否是同一个对象System.out.println(newInstance == Singleton.getInstance()); // false
}

(4)枚举

在程序启动时,会调用Singleton的空参构造器,实例化好一个Singleton对象赋给INSTANCE,之后再也不会实例化

好处:简洁,防止被反射等破坏

public enum Singleton {INSTANCE;Singleton() { System.out.println("枚举创建对象了"); }public static void main(String[] args) { /* test(); */ }public void test() {Singleton t1 = Singleton.INSTANCE;Singleton t2 = Singleton.INSTANCE;System.out.print("t1和t2的地址是否相同:" + t1 == t2);}
}
// 枚举创建对象了
// t1和t2的地址是否相同:true

枚举类默认继承了 Enum 类,在利用反射调用 newInstance() 时,会判断该类是否是一个枚举类,如果是,则抛出异常。

在读入Singleton对象时,每个枚举类型和枚举名字都是唯一的,所以在序列化时,仅仅只是对枚举的类型和变量名输出到文件中,在读入文件反序列化成对象时,利用 Enum 类的 valueOf(String name) 方法根据变量的名字查找对应的枚举对象。

参考: 三种单例模式写法及解析

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com