目录
一、单例模式
二、饿汉模式和懒汉模式
1、饿汉模式,线程安全
2、懒汉模式
懒汉模式1,线程不安全(不常用)
懒汉模式2,线程安全(不常用)
懒汉模式3,线程安全,双重校验(不常用)
懒汉模式4,线程安全,双重校验,volatile可见性,实现较为复杂
懒汉模式5,线程安全,静态内部类
懒汉模式6,线程安全,静态内部类,防止反射
3、readResolve方法
序列化测试
ObjectOutputStream是怎么校验readResolve()的
概括一下ObjectOutputStream().readObject()的整个大致流程
4、枚举方式,线程安全(不常用)
三、项目地址
一、单例模式
单例对象是一种常用的设计模式。这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。
在Java应用中,单例对象能保证在一个JVM中,该对象只有一个实例存在。这样的模式有几个好处:
1、某些类创建比较频繁,对于一些大型的对象,这是一笔很大的系统开销。
2、省去了new操作符,降低了系统内存的使用频率,减轻GC压力。
3、有些类如交易所的核心交易引擎,控制着交易流程,如果该类可以创建多个的话,系统完全乱了。
单例模式的特点总结就是
- 1、单例类只能有一个实例。
- 2、单例类必须自己创建自己的唯一实例。
- 3、单例类必须给所有其他对象提供这一实例。
二、饿汉模式和懒汉模式
饿汉式和懒汉式的区别,就是懒汉式比较懒,不先加载实例;饿汉式不管用户是否要使用该类的对象,就先创建好了一个实例放在内存中。
1、饿汉模式,线程安全
对象实例创建
代码语言:javascript复制package cn.zygxsq.design.module.singletonPattern.hungryMode;
/**
* 懒汉模式
* 原文:https://blog.csdn.net/qq_27471405/article/details/127167410
* Created by yjl on 2022/8/4.
*/
public class Singleton {
/* 持有私有静态实例,防止被引用*/
private static Singleton instance = new Singleton();
/* 私有构造方法,防止被实例化 */
private Singleton() {
}
/* 静态工程方法,返回Singleton实例 */
public static Singleton getInstance() {
return instance; // 原文:https://blog.csdn.net/qq_27471405/article/details/127167410
}
/* 如果该对象被用于序列化,可以保证对象在序列化前后保持一致 */
private Object readResolve() {
return instance;
}
/* 要操作的一些方法*/
public void dosomething(){
System.out.println("单例模式方法调用");
}
}
test调用Singleton实例中的方法
代码语言:javascript复制package cn.zygxsq.design.module.singletonPattern.hungryMode;
/**
* 原文:https://blog.csdn.net/qq_27471405/article/details/127167410
* Created by yjl on 2022/8/4.
*/
public class SingletonTest {
public static void main(String[] args) {
//不合法的构造函数
//编译时错误:构造函数 Singleton() 是不可见的
//Singleton instance = new Singleton(); // 原文:https://blog.csdn.net/qq_27471405/article/details/127167410
//获取唯一可用的对象
Singleton instance = Singleton.getInstance();
//调用方法
instance.dosomething();
}
}
执行结果
上述方法就是实现单例模式的其中一种(饿汉模式),这种方式比较常用,但是在类中不管用户是否要使用该类的对象,就先创建好了一个实例放在内存中,这就比较浪费内存。
优点:没有加锁,执行效率会提高。
缺点:类加载时就初始化,浪费内存。
它基于 classloader 机制避免了多线程的同步问题
我们试着不先创建对象的实例,到用的时候才去创建,这就需要用到懒汉模式
2、懒汉模式
懒汉模式1,线程不安全(不常用)
对象实例创建
代码语言:javascript复制package cn.zygxsq.design.module.singletonPattern.lazyMode.lazy1;
/**
* 懒汉模式1,线程不安全
* 原文:https://blog.csdn.net/qq_27471405/article/details/127167410
* Created by yjl on 2022/8/4.
*/
public class SingletonLazy1 {
/* 持有私有静态实例,防止被引用,此处赋值为null,目的是实现延迟加载 */
private static SingletonLazy1 instance = null;
/* 私有构造方法,防止被实例化 */
private SingletonLazy1() {
}
/* 静态工程方法,创建实例 */
public static SingletonLazy1 getInstance() {
if (instance == null) {
instance = new SingletonLazy1();
}
return instance;
}
/* 如果该对象被用于序列化,可以保证对象在序列化前后保持一致 */
private Object readResolve() {
return instance;
}
/* 要操作的一些方法*/
public void dosomething(){
System.out.println("单例模式方法调用");
}
}
test调用对象实例中的方法
代码语言:javascript复制package cn.zygxsq.design.module.singletonPattern.lazyMode.lazy1;
import cn.zygxsq.design.module.singletonPattern.hungryMode.Singleton;
/**
* 原文:https://blog.csdn.net/qq_27471405/article/details/127167410
* Created by yjl on 2022/8/4.
*/
public class SingletonTestLazy1 {
public static void main(String[] args) {
//不合法的构造函数
//编译时错误:构造函数 SingletonLazy1() 是不可见的
//SingletonLazy1 instance = new SingletonLazy1();
//获取唯一可用的对象
SingletonLazy1 instance = SingletonLazy1.getInstance(); // 原文:https://blog.csdn.net/qq_27471405/article/details/127167410
//调用方法
instance.dosomething();
}
}
执行结果
上述懒汉模式可以满足基本要求,但是,像这样毫无线程安全保护的类,如果我们把它放入多线程的环境下,肯定就会出现问题了,如何解决?我们首先会想到对getInstance方法加synchronized关键字
懒汉模式2,线程安全(不常用)
代码语言:javascript复制package cn.zygxsq.design.module.singletonPattern.lazyMode.lazy2;
/**
* 懒汉模式1,线程安全
* 原文:https://blog.csdn.net/qq_27471405/article/details/127167410
* Created by yjl on 2022/8/4.
*/
public class SingletonLazy2 {
/* 持有私有静态实例,防止被引用,此处赋值为null,目的是实现延迟加载 */
private static SingletonLazy2 instance = null;
/* 私有构造方法,防止被实例化 */
private SingletonLazy2() {
}
/* synchronized加锁,保证单例 */
public static synchronized SingletonLazy2 getInstance() {
if (instance == null) {
instance = new SingletonLazy2(); // 原文:https://blog.csdn.net/qq_27471405/article/details/127167410
}
return instance;
}
/* 如果该对象被用于序列化,可以保证对象在序列化前后保持一致 */
private Object readResolve() {
return instance;
}
/* 要操作的一些方法*/
public void dosomething(){
System.out.println("单例模式方法调用");
}
}
上述实现方式可以保证线程安全了,但是,synchronized作为修饰符在方法上使用,在性能上会有所下降,因为每次调用getInstance(),都要对对象上锁,事实上,只有在第一次创建对象的时候需要加锁,之后就不需要了,所以,需要改进
懒汉模式3,线程安全,双重校验(不常用)
代码语言:javascript复制package cn.zygxsq.design.module.singletonPattern.lazyMode.lazy3;
/**
* 懒汉模式1,线程安全
* 原文:https://blog.csdn.net/qq_27471405/article/details/127167410
* Created by yjl on 2022/8/4.
*/
public class SingletonLazy3 {
/* 持有私有静态实例,防止被引用,此处赋值为null,目的是实现延迟加载 */
private static SingletonLazy3 instance = null;
/* 私有构造方法,防止被实例化 */
private SingletonLazy3() {
}
/* synchronized加锁,保证单例 */
public static SingletonLazy3 getInstance() {
if (instance == null) {
synchronized (SingletonLazy3.class) {
if (instance == null) {
instance = new SingletonLazy3();
}
}
}
return instance;
}
/* 如果该对象被用于序列化,可以保证对象在序列化前后保持一致 */
private Object readResolve() {
return instance;
}
/* 要操作的一些方法*/
public void dosomething(){
System.out.println("单例模式方法调用");
}
}
上述实现方式似乎解决了之前提到的问题,将synchronized关键字加在了方法内部,也就是说当调用的时候是不需要加锁的,只有在instance为null,并创建对象的时候才需要加锁,性能有一定的提升。但是,这样的情况,还是有可能有问题的。会出现指令重排序的情况
看下面的情况:在Java指令中创建对象和赋值操作是分开进行的,也就是说
instance = new SingletonLazy3(); 这一段代码
语句并非是一个原子操作,在 JVM 中这句代码大概做了下面 3 件事情:
1、给 new的对象 分配内存
2、调用 Singleton 的构造函数来初始化成员变量
3、将引用instance指向分配的内存空间(执行完这步 instance 就为非 null 了)
但是在 JVM 的即时编译器中存在指令重排序的优化。也就是说上面的第二步和第三步的顺序是不能保证的,最终的执行顺序可能是 1-2-3 也可能是 1-3-2。如果是后者,则在 3 执行完毕、2 未执行之前,另外一个线程B抢夺到了CPU的执行权,这时instance已经是非null了(但却没有初始化),所以线程B会直接返回 instance,然后使用,结果就会出现问题了(因为对象还没有初始化)。
所以对于第三种进行优化的方式,就是对instance加一个volatile可见性 ,防止指令重排序
private volatile static SingletonLazy4 instance = null;
懒汉模式4,线程安全,双重校验,volatile可见性,实现较为复杂
代码语言:javascript复制package cn.zygxsq.design.module.singletonPattern.lazyMode.lazy4;
/**
* 懒汉模式4,线程安全,双重校验,volatile可见性,实现较为复杂
* 原文:https://blog.csdn.net/qq_27471405/article/details/127167410
* Created by yjl on 2022/8/4.
*/
public class SingletonLazy4 {
/* 持有私有静态实例,防止被引用,此处赋值为null,目的是实现延迟加载 */
private volatile static SingletonLazy4 instance = null;
/* 私有构造方法,防止被实例化 */
private SingletonLazy4() {
}
/* synchronized加锁,保证单例 */
public static SingletonLazy4 getInstance() {
if (instance == null) {
synchronized (SingletonLazy4.class) {
if (instance == null) {
instance = new SingletonLazy4(); // 原文:https://blog.csdn.net/qq_27471405/article/details/127167410
}
}
}
return instance;
}
/* 如果该对象被用于序列化,可以保证对象在序列化前后保持一致 */
private Object readResolve() {
return instance;
}
/* 要操作的一些方法*/
public void dosomething(){
System.out.println("单例模式方法调用");
}
}
懒汉模式4基本可以用了,但是实现起来比较繁琐,还可以有另外一种简单的方式,使用内部类来实现。(原创文章原文链接)
懒汉模式5,线程安全,静态内部类
使用内部类来维护单例的实现,JVM内部的机制能够保证当一个类被加载的时候,这个类的加载过程是线程互斥的(就是加载完毕后别的线程才能使用)。这样当我们第一次调用getInstance的时候,JVM能够帮我们保证instance只被创建一次,并且会保证把赋值给instance的内存初始化完毕,这样我们就不用担心上面的问题。同时该方法也只会在第一次调用的时候使用互斥机制,这样就解决了低性能问题。
代码语言:javascript复制package cn.zygxsq.design.module.singletonPattern.lazyMode.lazy5;
/**
* 懒汉模式5,线程安全,静态内部类
* 原文:https://blog.csdn.net/qq_27471405/article/details/127167410
* Created by yjl on 2022/8/4.
*/
public class SingletonLazy5 {
/* 私有构造方法,防止被实例化 */
private SingletonLazy5() {
}
/* 此处使用一个内部类来维护单例 */
private static class SingletonFactory {
private static SingletonLazy5 instance = new SingletonLazy5();
}
/* 获取实例 */
public static SingletonLazy5 getInstance() {
return SingletonFactory.instance; // 原文:https://blog.csdn.net/qq_27471405/article/details/127167410
}
/* 如果该对象被用于序列化,可以保证对象在序列化前后保持一致 */
private Object readResolve() {
return getInstance();
}
/* 要操作的一些方法*/
public void dosomething(){
System.out.println("单例模式方法调用");
}
}
这种懒汉模式静态内部类方式和饿汉模式很像,可以和饿汉模式对比着看一下,饿汉模式只要 Singleton 类被装载了,那么 instance 就会被实例化(没有达到懒加载效果),而这种懒汉方式5静态内部类方式是 SingletonLazy5 类被装载了,instance 不一定被初始化。因为 SingletonFactory 类没有被主动使用,只有通过显式调用 getInstance 方法时,才会显式装载 SingletonFactory 类,从而实例化 instance。想象一下,如果实例化 instance 很消耗资源,所以想让它延迟加载,另外一方面,又不希望在 Singleton 类加载时就实例化,因为不能确保 Singleton 类还可能在其他的地方被主动使用从而被加载,那么这个时候实例化 instance 显然是不合适的。这个时候,这种方式相比饿汉模式方式就显得很合理。
懒汉模式5静态内部类方式已经很牛了,但是如果在构造函数中抛出异常,实例将永远得不到创建,也会出错。
还有,懒汉模式5静态内部类已经很牛了,但是如果遇到反射调用,我们可以使用反射去创建这个类的对象,即使它的构造器是私有的,我们也是可以调用到的,那也可以创建多个实例。那么这个时候我们就需要再次修改代码去访问别人反射调用构造器。
所以说,十分完美的东西是没有的,我们只能根据实际情况,选择最适合自己应用场景的实现方法。
反射调用demo
代码语言:javascript复制package cn.zygxsq.design.module.singletonPattern.lazyMode.lazy5;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
/**
* 反射获取私有构造函数,创建多个实例
* 原文:https://blog.csdn.net/qq_27471405/article/details/127167410
* Created by yjl on 2022/8/4.
*/
public class SingletonTestLazy5Reflection {
public static void main(String[] args) throws Exception{
// 1:通过Class的静态方法forName加载
Class aClass = Class.forName("cn.zygxsq.design.module.singletonPattern.lazyMode.lazy5.SingletonLazy5");
//获取私有构造方法
//Constructor con = c.getConstructor(String.class);
//NoSuchMethodException没有这个方法异常
//原因是一开始我们使用的方法只能获取公共的,下面这种方式就可以
Constructor con = aClass.getDeclaredConstructor();
//用该私有方法创建对象
//IllegalAccessException:非法访问异常
//暴力访问
con.setAccessible(true);//值为true则指示反射的对象在使用是应该取消Java语言访问检查
// 实例化对象的方法
Object o1 = con.newInstance();
System.out.println(o1);
//Method m = o1.getClass().getDeclaredMethod("dosomething", null);
//访问方法
Method m = aClass.getDeclaredMethod("dosomething", null);
//调用方法
m.invoke(o1, null);
// 第二次创建对象
Class aClass2 = Class.forName("cn.zygxsq.design.module.singletonPattern.lazyMode.lazy5.SingletonLazy5");
Constructor con2 = aClass2.getDeclaredConstructor();
//用该私有方法创建对象
//IllegalAccessException:非法访问异常
//暴力访问
con2.setAccessible(true);//值为true则指示反射的对象在使用是应该取消Java语言访问检查
// 实例化对象的方法
Object o2 = con2.newInstance();
System.out.println(o2);
}
}
执行结果
可以看到多个对象是不同的
懒汉模式6,线程安全,静态内部类,防止反射
我们为了避免别人反射调用,我们修改一下构造器为下面这样的
private SingletonLazy6() { if(!flag){ flag = true; }else{ throw new RuntimeException("不能多次创建单例对象"); } }
demo
代码语言:javascript复制package cn.zygxsq.design.module.singletonPattern.lazyMode.lazy6;
/**
* 懒汉模式6,线程安全,静态内部类,防止反射多次
* 原文:https://blog.csdn.net/qq_27471405/article/details/127167410
* Created by yjl on 2022/8/4.
*/
public class SingletonLazy6 {
private static boolean flag;
/* 私有构造方法,防止被实例化 */
private SingletonLazy6() {
if(!flag){
flag = true;
}else{
throw new RuntimeException("不能多次创建单例对象");
}
}
/* 此处使用一个内部类来维护单例 */
private static class SingletonFactory {
private static SingletonLazy6 instance = new SingletonLazy6();
}
/* 获取实例 */
public static SingletonLazy6 getInstance() {
return SingletonFactory.instance; // 原文:https://blog.csdn.net/qq_27471405/article/details/127167410
}
/* 如果该对象被用于序列化,可以保证对象在序列化前后保持一致 */
private Object readResolve() {
return getInstance();
}
/* 要操作的一些方法*/
public void dosomething(){
System.out.println("单例模式方法调用");
}
}
test反射多次调用测试
代码语言:javascript复制package cn.zygxsq.design.module.singletonPattern.lazyMode.lazy6;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
/**
* 反射获取私有构造函数,创建多个实例
* 原文:https://blog.csdn.net/qq_27471405/article/details/127167410
* Created by yjl on 2022/8/4.
*/
public class SingletonTestLazy6Reflection {
public static void main(String[] args) throws Exception{
// 1:通过Class的静态方法forName加载
Class aClass = Class.forName("cn.zygxsq.design.module.singletonPattern.lazyMode.lazy6.SingletonLazy6");
//获取私有构造方法
//Constructor con = c.getConstructor(String.class); // 原文:https://blog.csdn.net/qq_27471405/article/details/127167410
//NoSuchMethodException没有这个方法异常
//原因是一开始我们使用的方法只能获取公共的,下面这种方式就可以
Constructor con = aClass.getDeclaredConstructor();
//用该私有方法创建对象
//IllegalAccessException:非法访问异常
//暴力访问
con.setAccessible(true);//值为true则指示反射的对象在使用是应该取消Java语言访问检查
// 实例化对象的方法
Object o1 = con.newInstance();
System.out.println(o1);
//Method m = o1.getClass().getDeclaredMethod("dosomething", null);
//访问方法
Method m = aClass.getDeclaredMethod("dosomething", null);
//调用方法
m.invoke(o1, null);
// 第二次创建对象
Class aClass2 = Class.forName("cn.zygxsq.design.module.singletonPattern.lazyMode.lazy6.SingletonLazy6");
Constructor con2 = aClass2.getDeclaredConstructor();
//用该私有方法创建对象
//IllegalAccessException:非法访问异常
//暴力访问
con2.setAccessible(true);//值为true则指示反射的对象在使用是应该取消Java语言访问检查
// 实例化对象的方法
Object o2 = con2.newInstance();
System.out.println(o2);
}
}
执行结果
3、readResolve方法
懒汉模式6静态内部类防止反射,反射的问题处理完了之后,这里还有一个问题,就是如果把单例对象进行序列化然后再反序列化,那么内存中就会出现俩个一样的单例对象,只是内存地址不同。这种情况我们可以使用readResolve方法来防止。
private Object readResolve(){.....}
ObjectInputStream 会检查对象的class是否定义了readResolve方法。如果定义了,将由readResolve方法指定返回的对象。返回对象的类型一定要是兼容的,否则会抛出ClassCastException 。
序列化测试
实现一下Serializable
代码语言:javascript复制package cn.zygxsq.design.module.singletonPattern.lazyMode.lazy6.testReadResolve;
import java.io.Serializable;
/**
* 懒汉模式6,线程安全,静态内部类,防止反射多次
* 原文:https://blog.csdn.net/qq_27471405/article/details/127167410
* Created by yjl on 2022/8/4.
*/
public class SingletonLazy6Serializable implements Serializable {
private static boolean flag;
/* 私有构造方法,防止被实例化 */
private SingletonLazy6Serializable() {
if(!flag){
flag = true;
}else{
throw new RuntimeException("不能多次创建单例对象");
}
}
/* 此处使用一个内部类来维护单例 */
private static class SingletonFactory {
private static SingletonLazy6Serializable instance = new SingletonLazy6Serializable();
}
/* 获取实例 */
public static SingletonLazy6Serializable getInstance() {
return SingletonFactory.instance; // 原文:https://blog.csdn.net/qq_27471405/article/details/127167410
}
/* 如果该对象被用于序列化,可以保证对象在序列化前后保持一致 */
private Object readResolve() {
return getInstance();
}
/* 要操作的一些方法*/
public void dosomething(){
System.out.println("单例模式方法调用");
}
}
test调用SingletonLazy6Serializable
代码语言:javascript复制package cn.zygxsq.design.module.singletonPattern.lazyMode.lazy6.testReadResolve;
import cn.zygxsq.design.module.singletonPattern.lazyMode.lazy6.SingletonLazy6;
import java.io.*;
/**
* 反射获取私有构造函数,创建多个实例
* 原文:https://blog.csdn.net/qq_27471405/article/details/127167410
* Created by yjl on 2022/8/4.
*/
public class SingletonTestLazy6ReadResolve {
public static void main(String[] args) throws Exception{
// 将SingletonLazy6Serializable类的readResolve()方法注释执行一下和不注释执行一下
// 查看打印结果
SingletonTestLazy6ReadResolve singletonTestLazy6ReadResolve = new SingletonTestLazy6ReadResolve();
singletonTestLazy6ReadResolve.copy();
}
//测试方式,把单例对象序列化后再反序列化从而获得一个新的对象 就相当于复制了一个单例对象
public SingletonLazy6Serializable copy() throws Exception{
System.out.println(SingletonLazy6Serializable.getInstance());
ByteArrayOutputStream os = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(os);
oos.writeObject(SingletonLazy6Serializable.getInstance()); // 原文:https://blog.csdn.net/qq_27471405/article/details/127167410
InputStream is = new ByteArrayInputStream(os.toByteArray());
ObjectInputStream ois = new ObjectInputStream(is);
SingletonLazy6Serializable obj = (SingletonLazy6Serializable) ois.readObject();
System.out.println(obj);
return obj;
}
}
将SingletonLazy6Serializable类的readResolve()方法注释执行一下和不注释执行一下,分别查看一下打印结果
注释readResolve()方法执行结果,返回的是内存地址不同
不注释readResolve()方法执行结果,返回的内存地址相同
这就是为什么我将单例模式都要加一个readResolve()方法了,这个你们在其他博客基本上是很难见到的,我这里(小小鱼儿小小林)讲得稍微更详细更清楚一点,其他人估计只能讲到上述的饿汉模式和懒汉模式5静态内部类,基本上已经很全很厉害了,其实懒汉模式5静态内部类基本上也够用了。能解决系统的97%了
我这里额外加了一个懒汉模式6静态内部类防止反射,并且讲述了必须加一个readResolve()方法,不然会有序列化问题。
ObjectOutputStream是怎么校验readResolve()的
可以看一下readObject()方法
再看一下
Object obj = readObject0(false);
看一下readOrdinaryObject()方法,很重要
代码语言:javascript复制case TC_OBJECT: return checkResolve(readOrdinaryObject(unshared));
private Object readOrdinaryObject(boolean unshared)
throws IOException
{
if (bin.readByte() != TC_OBJECT) {
throw new InternalError();
}
ObjectStreamClass desc = readClassDesc(false);
desc.checkDeserialize();
Class<?> cl = desc.forClass();
if (cl == String.class || cl == Class.class
|| cl == ObjectStreamClass.class) {
throw new InvalidClassException("invalid class descriptor");
}
Object obj;
try {
obj = desc.isInstantiable() ? desc.newInstance() : null;
} catch (Exception ex) {
throw (IOException) new InvalidClassException(
desc.forClass().getName(),
"unable to create instance").initCause(ex);
}
passHandle = handles.assign(unshared ? unsharedMarker : obj);
ClassNotFoundException resolveEx = desc.getResolveException();
if (resolveEx != null) {
handles.markException(passHandle, resolveEx);
}
if (desc.isExternalizable()) {
readExternalData((Externalizable) obj, desc);
} else {
readSerialData(obj, desc);
}
handles.finish(passHandle);
if (obj != null &&
handles.lookupException(passHandle) == null &&
desc.hasReadResolveMethod())
{
Object rep = desc.invokeReadResolve(obj);
if (unshared && rep.getClass().isArray()) {
rep = cloneArray(rep);
}
if (rep != obj) {
// Filter the replacement object
if (rep != null) {
if (rep.getClass().isArray()) {
filterCheck(rep.getClass(), Array.getLength(rep));
} else {
filterCheck(rep.getClass(), -1);
}
}
handles.setObject(passHandle, obj = rep);
}
}
return obj;
}
看一下下面的截图
readOrdinaryObject(boolean unshared)方法中获取单例类的ObjectStreamClass对象desc,判断对象是否能实例化。可以则进行实例化,至此单例类进行了第一次实例化,对象名为obj
第一次实例化完成后,通过反射寻找该单例类中的readResolve()方法,没有则直接返回obj对象。这就是我们对没有readResolve()方法的类进行序列化后生成不同对象的原因。 因为我们有定义readResolve()方法,desc通过invokeReadResolve(Object obj)方法调用readResolve()方法获取单例对象instance,将他赋值给rep
概括一下ObjectOutputStream().readObject()的整个大致流程
代码语言:javascript复制1.我们在单例类中定义一个readResolve()方法,用于返回instance对象。
2.反序列化获取单例类对象时调用readObject()方法。
3.readObject()方法中调用readObject0()方法。
4.readObject0()方法中调用readOrdinaryObject(boolean unshared)方法。
5.readOrdinaryObject(boolean unshared)方法中获取单例类的ObjectStreamClass对象desc,判断对象是否能实例化。可以则进行实例化,至此单例类进行了第一次实例化,对象名为obj。
6.第一次实例化完成后,通过反射寻找该单例类中的readResolve()方法,没有则直接返回obj对象。这就是我们对没有readResolve()方法的类进行序列化后生成不同对象的原因。
7.因为我们有定义readResolve()方法,desc通过invokeReadResolve(Object obj)方法调用readResolve()方法获取单例对象instance,将他赋值给rep。
8.rep与obj进行比较,由于obj是反射获取的对象,当然与rep不等,于是将rep的值instance赋值给obj,将obj返回,返回对象instance也就保证了单例。
9.简而言之就是当我们通过反序列化readObject()方法获取对象时会去寻找readResolve()方法,如果该方法不存在则直接返回新对象,如果该方法存在则按该方法的内容返回对象。
懒汉模式6静态内部类防止反射写起来还是有点复杂的,其实还有一种更简单的方式,那就是用枚举的方式
4、枚举方式,线程安全(不常用)
代码语言:javascript复制package cn.zygxsq.design.module.singletonPattern.enumMode;
/**
* 单例模式,枚举方式
* 原文:https://blog.csdn.net/qq_27471405/article/details/127167410
* Created by yjl on 2022/8/5.
*/
public enum SingletonEnum {
INSTANCE;
/* 要操作的一些方法*/
public void dosomething(){
System.out.println("单例模式方法调用"); // 原文:https://blog.csdn.net/qq_27471405/article/details/127167410
}
}
调用的话就跟枚举方式调用的方式一样
代码语言:javascript复制package cn.zygxsq.design.module.singletonPattern.enumMode;
/**
* Created by yjl on 2022/8/5.
*/
public class SingletonEnumTest {
public static void main(String[] args) {
SingletonEnum instance = SingletonEnum.INSTANCE;
instance.dosomething();
}
}
这种实现方式还没有被广泛采用,但这是实现单例模式的最佳方法。它更简洁,自动支持序列化机制,绝对防止多次实例化。
这种方式是 Effective Java 作者 Josh Bloch 提倡的方式,它不仅能避免多线程同步问题,而且还自动支持序列化机制,防止反序列化重新创建新的对象,绝对防止多次实例化。不过,由于 JDK1.5 之后才加入 enum 特性,用这种方式写不免让人感觉生疏,在实际工作中,也很少用。
不能通过 reflection attack 来调用私有构造方法。
三、项目地址
以上代码在下面地址中有全部代码
https://github.com/jalenFish/design-patterns/
参考文章:
单例模式 | 菜鸟教程
单例设计模式之readResolve()方法_♀桂圆的博客-CSDN博客
感谢原作者的分享,让技术人能够更快的解决问题