java设计模式之单例模式|单例模式之饿汉模式、懒汉模式、枚举方式|最详细的6种懒汉模式详解

article/2025/9/22 2:30:20

目录

一、单例模式

二、饿汉模式和懒汉模式

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、饿汉模式,线程安全

对象实例创建

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实例中的方法

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,线程不安全(不常用)

对象实例创建

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调用对象实例中的方法

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,线程安全(不常用)

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,线程安全,双重校验(不常用)

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可见性,实现较为复杂

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的内存初始化完毕,这样我们就不用担心上面的问题。同时该方法也只会在第一次调用的时候使用互斥机制,这样就解决了低性能问题。

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

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

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反射多次调用测试

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 

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 

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/127167410InputStream 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()方法,很重要

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 objectif (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()的整个大致流程

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、枚举方式,线程安全(不常用)

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}
}

调用的话就跟枚举方式调用的方式一样

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博客

感谢原作者的分享,让技术人能够更快的解决问题


http://chatgpt.dhexx.cn/article/kQ7Kg9I7.shtml

相关文章

全志F1C100s主线linux入坑记录 (10)调试串口更改

调试串口更改 百度网站 提示&#xff1a;写完文章后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 调试串口更改前言uboot 修改一、修改设备树二、修改文件3. 修改内核传递参数 内核修改参考 前言 未完成版本 未完成版本 未完成版本 未完成…

f1c100s 调试问题汇总

问题 usb无法识别 windows显示无法识别的usb设备 解决&#xff1a; 卸载设备&#xff0c;插拔一下&#xff0c;就可以识别了&#xff0c;之后就会自动安装驱动。 umount失败 fuser ./d2 可以显示出当前哪个程序在使用磁盘上的某个文件、挂载点、甚至网络端口&#xff0c;并…

【f1c200s/f1c100s】FT5426触摸屏驱动适配

【f1c200s/f1c100s】FT5426触摸屏驱动适配 前言设备树配置IIC控制器FT5426设备树配置 内核配置结果 前言 嵌入式linux下的触摸屏驱动是基于input子系统的&#xff0c;当触摸发生时&#xff0c;内核上报触摸事件至用户层。我使用的显示屏是正点原子的7寸RGB接口显示屏&#xff…

f1c100s开发笔记

f1c100s开发笔记 全志芯片相关的论坛帖f1c100s移植帖交叉编译器的安装uboot的编译适配配置开始编译uboot编译遇坑 2020-05-20 09:56:15 星期四 全志芯片相关的论坛帖 https://whycan.cn/t_3019.html#p25005 f1c100s移植帖 https://whycan.cn/t_3211.html 交叉编译器的安装 …

全志F1C100S/F1C200S学习笔记(1)——基础简介及资料

文章目录 一、芯片概览二、芯片框图三、芯片规格四、资料:五、仓库:一、芯片概览 二、芯片框图 三、芯片规格 功能描述CPUARM9 CPU architecture16KByte D

f1c100linux系统吗,全志F1C100s怎么样 F1C100s芯片参数介绍

全志F1C100s芯片怎么样&#xff0c;F1C100s处理器好用吗&#xff1f;F1C100s是720P高清多媒体处理器。下面带来F1C100s芯片的具体参数&#xff0c;准备入手搭载F1C100s芯片设备的用户可以参考一下。 F1C100s芯片架构图 F1C100s特性介绍 支持H.264 1920x108030fps 解码 支持MJPE…

全志F1C100S的BROM研究

全志f1c100s是个性价比很高的芯片&#xff0c;但是对一般人不太友好的是它的资料开放的太少了。 网上找不到完整版的用户手册&#xff0c;只能从有限的手册文档和参考代码旁敲侧击&#xff0c;反向猜测。 关于它的BROM网上的手册内容很少。 手册上只有短短3句话&#xff1a; 具…

10、Lctech Pi(F1C200S)驱动电阻屏触摸芯片ns2009(ts2007),buildroot配置tslib(CherryPi,Mangopi,F1C100S)

本次主要参考&#xff1a; https://github.com/mangopi-sbc/buildroot-mangopi-r https://blog.csdn.net/qq_35031421/article/details/113436888 https://blog.csdn.net/dancheqishi23/article/details/116498088 &#xff08;如果方便请给这几位大佬一个关注&#xff09; 开…

F1C100S自制开发板调试过程

疫情&#xff0c;等了好久板子终于到了。 我这里使用的是坑网大佬提供的tiny200开发包&#xff0c;用的芒果派R3配置文件 1&#xff0c;配置其的介质&#xff0c;我板子上用的是nor-spi-flash,所以需要在设备树里面屏蔽掉nand-flash相关的节点&#xff0c;否则启动会有错误。 …

F1C100S(Lichee Nano)触摸屏 (GT9147)

1、前提 Ubuntu 环境版本 (18.04) Linux ubuntu 5.4.0-131-generic #147~18.04.1-Ubuntu SMP Sat Oct 15 13:10:18 UTC 2022 x86_64 x86_64 x86_64 GNU/LinuxARM GCC版本 gcc version 7.2.1 20171011 (Linaro GCC 7.2-2017.11)F1C100S Linux版本 (linux-nano-5.2-tf) 链接 …

全志F1C100s主线linux入坑记录 (5)LVGL8.2移植

LVGL8.2移植 百度网站 文章目录 LVGL8.2移植一、安装VScode二、安装lvgl模拟器二、F1c100s 移植lvgl参考 一、安装VScode 进入VScode官网下载安装包&#xff0c;如果最新版本安装有问题可以安装老版本的 https://code.visualstudio.com/使用命令行安装VScode sudo dpkg -i …

全志F1C100s主线linux入坑记录 (7)GBA模拟器移植

GBA模拟器移植 百度网站 文章目录 GBA模拟器移植一、下载gpsp 源代碼二、gpsp环境配置参考 一、下载gpsp 源代碼 gpsp源码 解压文件 7z x gpsp.7z 二、gpsp环境配置 gpsp需要SDL环境我们先在bulidroot中添加SDL包 make menuconfigTarget packages ---> Graphic libra…

9、Lctech Pi(F1C200S)开启I2C0(CherryPi,Mangopi,F1C100S)

本次主要参考&#xff1a; https://github.com/mangopi-sbc/buildroot-mangopi-r https://wiki.sipeed.com/soft/Lichee/zh/Nano-Doc-Backup/index.html &#xff08;如果方便请给这几位大佬一个关注&#xff09; 注意代码块之间的空行 配置设备树文件 1、打开linux-5.7.1/a…

全志F1C100s主线linux入坑记录 (3)适配其他分辨率的LCD

适配其他分辨率的LCD 百度网站 文章目录 适配其他分辨率的LCD一、修改U-boot屏幕参数二、修改linux内核文件三、测试效果 一、修改U-boot屏幕参数 修改对应屏幕的参数&#xff08;我这里是1024*600的屏幕&#xff09; 修改 -> ARM architecture -> Enable graphical ub…

全志F1C100S从零开发记录(1)

1.刚买到板子&#xff0c;开箱如下图所示&#xff1a; 2.管脚焊接&#xff08;焊接串口&#xff0c;用来看串口打印信息&#xff09;&#xff0c;5v供电&#xff1a; 3.通过usb转ttl接到电脑上&#xff1a; . 4.设置串口波特率115200 &#xff0c;打开串口&#xff0c;上电出…

7、Lctech Pi(F1C200S)开启RNDIS,通过USB与电脑联网(CherryPi,Mangopi,F1C100S)

本次主要参考&#xff1a; 荔枝nano开启RNDIS驱动&#xff0c;无需补丁。 https://github.com/peng-zhihui/Planck-Pi#head23 https://github.com/mangopi-sbc/buildroot-mangopi-r https://www.cnblogs.com/listenscience/p/13758272.html &#xff08;如果方便请给这几位大佬…

全志F1C200S F1C100S 介绍

很久以前发现了一颗性价比极高而且比较好玩的SOC&#xff0c;加群请仔细阅读本博客&#xff08;见DKTool界面&#xff0c;请备注“来自博客”&#xff09; 那就是全志F1C100S F1C200S&#xff0c;其中F1C100S内置32MB DDR1内存&#xff0c;F1C200S内置64MB DDR1内存。 这个片…

6、Lctech Pi(F1C200S)4.3寸(480*272)16位RGB565LCD屏驱动适配(CherryPi,Mangopi,F1C100S)

本次主要参考&#xff1a; https://blog.csdn.net/wending1986/article/details/106837597/ http://blog.chinaunix.net/uid-20543672-id-3246283.html https://wiki.sipeed.com/soft/Lichee/zh/Nano-Doc-Backup/index.html 李山文大佬 &#xff08;如果方便请给这几位大佬一个…

【F1C100S】编译启动所需的uboot,kernel,rootfs

个人画了块没有连接任何其他模块的F1C100S开发板&#xff0c;没有链接任何外设&#xff0c;本文是编译所有启动所需的三大件。&#xff08;只要编译出三个东西复制到sd卡就可启动&#xff0c;不需要制作成镜像img文件&#xff09; 环境&#xff1a;使用安装了ubuntu18的电脑进…

荔枝派nano(f1c100s)从零开始TF卡启动(附源码下载)

本文所使用的所有文件都已打包到SDK中&#xff0c;可以直接使用&#xff0c;有些细节我已省略&#xff0c;直接使用我包里的文件即可。 (无需积分&#xff0c;免费下载&#xff0c;里面的东西都来源于官方&#xff09; 觉得可以的&#xff0c;可以点个赞、点个关注&#xff0…