1、基本介绍单例模式Singleton Pattern是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式它提供了一种创建对象的最佳方式。这种模式涉及到一个单一的类该类负责创建自己的对象同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式可以直接访问不需要实例化该类的对象。注意单例类只能有一个实例。单例类必须自己创建自己的唯一实例。单例类必须给所有其他对象提供这一实例。意图保证一个类仅有一个实例并提供一个访问它的全局访问点。主要解决一个全局使用的类频繁地创建与销毁。何时使用当您想控制实例数目节省系统资源的时候。如何解决判断系统是否已经有这个单例如果有则返回如果没有则创建。关键代码构造函数是私有的。应用实例一个班级只有一个班主任。Windows 是多进程多线程的在操作一个文件的时候就不可避免地出现多个进程或线程同时操作一个文件的现象所以所有文件的处理必须通过唯一的实例来进行。一些设备管理器常常设计为单例模式比如一个电脑有两台打印机在输出的时候就要处理不能两台打印机打印同一个文件。优点在内存里只有一个实例减少了内存的开销尤其是频繁的创建和销毁实例比如管理学院首页页面缓存。避免对资源的多重占用比如写文件操作。缺点没有接口不能继承与单一职责原则冲突一个类应该只关心内部逻辑而不关心外面怎么样来实例化。使用场景要求生产唯一序列号。WEB 中的计数器不用每次刷新都在数据库里加一次用单例先缓存起来。创建的一个对象需要消耗的资源过多比如 I/O 与数据库的连接等。注意事项getInstance() 方法中需要使用同步锁 synchronized (Singleton.class) 防止多线程同时进入造成 instance 被多次实例化。单例设计模式分类两种饿汉式类加载就会导致该单实例对象被创建懒汉式类加载不会导致该单实例对象被创建而是首次使用该对象时才会创建2、饿汉式饿汉式在类加载的过程导致该单实例对象被创建虚拟机会保证类加载的线程安全但是如果只是为了加载该类不需要实例则会造成内存的浪费静态变量的方式publicfinalclassSingleton{// 私有构造方法privateSingleton(){}// 在成员位置创建该类的对象privatestaticfinalSingletoninstancenewSingleton();// 对外提供静态方法获取该对象publicstaticSingletongetInstance(){returninstance;}// 解决序列化问题protectedObjectreadResolve(){returnINSTANCE;}}加 final 修饰所以不会被子类继承防止子类中不适当的行为覆盖父类的方法破坏了单例防止反序列化破坏单例的方式对单例声明 transient然后实现 readObject(ObjectInputStream in) 方法复用原来的单例条件访问权限为 private/protected、返回值必须是 Object、异常可以不抛实现 readResolve() 方法当 JVM 从内存中反序列化地组装一个新对象就会自动调用 readResolve 方法返回原来单例构造方法设置为私有防止其他类无限创建对象但是不能防止反射破坏静态变量初始化在类加载时完成由 JVM 保证线程安全能保证单例对象创建时的安全提供静态方法而不是直接将 INSTANCE 设置为 public体现了更好的封装性、提供泛型支持、可以改进成懒汉单例设计静态代码块的方式publicclassSingleton{// 私有构造方法privateSingleton(){}// 在成员位置创建该类的对象privatestaticSingletoninstance;static{instancenewSingleton();}// 对外提供静态方法获取该对象publicstaticSingletongetInstance(){returninstance;}}枚举方式枚举类型是所用单例实现中唯一一种不会被破坏的单例实现模式publicenumSingleton{INSTANCE;publicvoiddoSomething(){System.out.println(doSomething);}}publicstaticvoidmain(String[]args){Singleton.INSTANCE.doSomething();}问题1枚举单例是如何限制实例个数的每个枚举项都是一个实例是一个静态成员变量问题2枚举单例在创建时是否有并发问题否问题3枚举单例能否被反射破坏单例否反射创建对象时判断是枚举类型就直接抛出异常问题4枚举单例能否被反序列化破坏单例否问题5枚举单例属于懒汉式还是饿汉式饿汉式问题6枚举单例如果希望加入一些单例创建时的初始化逻辑该如何做添加构造方法反编译结果publicfinalclassSingletonextendsjava.lang.EnumSingleton{// Enum实现序列化接口publicstaticfinalSingletonINSTANCEnewSingleton();}3、懒汉式线程不安全publicclassSingleton{// 私有构造方法privateSingleton(){}// 在成员位置创建该类的对象privatestaticSingletoninstance;// 对外提供静态方法获取该对象publicstaticSingletongetInstance(){if(instancenull){// 多线程环境会出现线程安全问题可能多个线程同时进入这里instancenewSingleton();}returninstance;}}双端检锁机制在多线程的情况下可能会出现空指针问题出现问题的原因是 JVM 在实例化对象的时候会进行优化和指令重排序操作所以需要使用volatile关键字publicclassSingleton{// 私有构造方法privateSingleton(){}privatestaticvolatileSingletoninstance;// 对外提供静态方法获取该对象publicstaticSingletongetInstance(){// 第一次判断如果instance不为null不进入抢锁阶段直接返回实例if(instancenull){synchronized(Singleton.class){// 抢到锁之后再次判断是否为nullif(instancenull){instancenewSingleton();}}}returninstance;}}静态内部类方式publicclassSingleton{// 私有构造方法privateSingleton(){}privatestaticclassSingletonHolder{privatestaticfinalSingletonINSTANCEnewSingleton();}// 对外提供静态方法获取该对象publicstaticSingletongetInstance(){returnSingletonHolder.INSTANCE;}}内部类属于懒汉式类加载本身就是懒惰的首次调用时加载然后对单例进行初始化类加载的时候方法不会被调用所以不会触发 getInstance 方法调用 invokestatic 指令对内部类进行加载加载的时候字节码常量池会被加入类的运行时常量池解析工作是将常量池中的符号引用解析成直接引用但是解析过程不一定非得在类加载时完成可以延迟到运行时进行所以静态内部类实现单例会延迟加载没有线程安全问题静态变量初始化在类加载时完成由 JVM 保证线程安全4、破坏单例反序列化将单例对象序列化再反序列化对象从内存反序列化到程序中会重新创建一个对象通过反序列化得到的对象是不同的对象而且得到的对象不是通过构造器得到的反序列化得到的对象不执行构造器SingletonpublicclassSingletonimplementsSerializable{//实现序列化接口// 私有构造方法privateSingleton(){}privatestaticclassSingletonHolder{privatestaticfinalSingletonINSTANCEnewSingleton();}// 对外提供静态方法获取该对象publicstaticSingletongetInstance(){returnSingletonHolder.INSTANCE;}}序列化publicclassTest{publicstaticvoidmain(String[]args)throwsException{//往文件中写对象//writeObject2File();//从文件中读取对象Singletons1readObjectFromFile();Singletons2readObjectFromFile();//判断两个反序列化后的对象是否是同一个对象System.out.println(s1s2);}privatestaticSingletonreadObjectFromFile()throwsException{//创建对象输入流对象ObjectInputStreamoisnewObjectInputStream(newFileInputStream(C://a.txt));//第一个读取Singleton对象Singletoninstance(Singleton)ois.readObject();returninstance;}publicstaticvoidwriteObject2File()throwsException{//获取Singleton类的对象SingletoninstanceSingleton.getInstance();//创建对象输出流ObjectOutputStreamoosnewObjectOutputStream(newFileOutputStream(C://a.txt));//将instance对象写出到文件中oos.writeObject(instance);}}解决方法在 Singleton 类中添加readResolve()方法在反序列化时被反射调用如果定义了这个方法就返回这个方法的值如果没有定义则返回新创建的对象privateObjectreadResolve(){returnSingletonHolder.INSTANCE;}ObjectInputStream 类源码分析publicfinalObjectreadObject()throwsIOException,ClassNotFoundException{//...ObjectobjreadObject0(false);//重点查看readObject0方法}privateObjectreadObject0(booleanunshared)throwsIOException{try{switch(tc){caseTC_OBJECT:returncheckResolve(readOrdinaryObject(unshared));}}}privateObjectreadOrdinaryObject(booleanunshared)throwsIOException{// isInstantiable 返回true执行 desc.newInstance()通过反射创建新的单例类objdesc.isInstantiable()?desc.newInstance():null;// 添加 readResolve 方法后 desc.hasReadResolveMethod() 方法执行结果为trueif(obj!nullhandles.lookupException(passHandle)nulldesc.hasReadResolveMethod()){// 通过反射调用 Singleton 类中的 readResolve 方法将返回值赋值给rep变量// 多次调用ObjectInputStream类中的readObject方法本质调用定义的readResolve方法返回的是同一个对象。Objectrepdesc.invokeReadResolve(obj);}returnobj;}反射破解反射publicclassTest{publicstaticvoidmain(String[]args)throwsException{//获取Singleton类的字节码对象ClassclazzSingleton.class;//获取Singleton类的私有无参构造方法对象Constructorconstructorclazz.getDeclaredConstructor();//取消访问检查constructor.setAccessible(true);//创建Singleton类的对象s1Singletons1(Singleton)constructor.newInstance();//创建Singleton类的对象s2Singletons2(Singleton)constructor.newInstance();//判断通过反射创建的两个Singleton对象是否是同一个对象System.out.println(s1s2);//false}}反射方式破解单例的解决方法publicclassSingleton{privatestaticvolatileSingletoninstance;// 私有构造方法privateSingleton(){// 反射破解单例模式需要添加的代码if(instance!null){thrownewRuntimeException();}}// 对外提供静态方法获取该对象publicstaticSingletongetInstance(){if(instance!null){returninstance;}synchronized(Singleton.class){if(instance!null){returninstance;}instancenewSingleton();returninstance;}}}RuntimeRuntime 类就是使用的单例设计模式中的饿汉式publicclassRuntime{privatestaticRuntimecurrentRuntimenewRuntime();publicstaticRuntimegetRuntime(){returncurrentRuntime;}privateRuntime(){}...}使用 RuntimepublicclassRuntimeDemo{publicstaticvoidmain(String[]args)throwsIOException{//获取Runtime类对象RuntimeruntimeRuntime.getRuntime();//返回 Java 虚拟机中的内存总量。System.out.println(runtime.totalMemory());//返回 Java 虚拟机试图使用的最大内存量。System.out.println(runtime.maxMemory());//创建一个新的进程执行指定的字符串命令返回进程对象Processprocessruntime.exec(ipconfig);//获取命令执行后的结果通过输入流获取InputStreaminputStreamprocess.getInputStream();byte[]arrnewbyte[1024*1024*100];intbinputStream.read(arr);System.out.println(newString(arr,0,b,gbk));}}最近看到一个很扎心的现象企业越来越关注开发效率而 AI 正在成为新的生产力工具。同样的需求会使用 AI 的工程师往往能够更快完成设计、编码和测试工作。与其担心被 AI 替代不如尽早学会驾驭 AI。最近我不仅在学习 Java 底层还在学习一些人工智能的知识发现了一个不错的 AI 学习网站内容通俗易懂比较适合程序员快速上手感兴趣的话也可以看看人工智能学习网本篇文章到这里就结束了最后送大家一句话白驹过隙沧海桑田
单例模式:让每个对象都成为不可替代的明星
发布时间:2026/6/13 23:38:12
1、基本介绍单例模式Singleton Pattern是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式它提供了一种创建对象的最佳方式。这种模式涉及到一个单一的类该类负责创建自己的对象同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式可以直接访问不需要实例化该类的对象。注意单例类只能有一个实例。单例类必须自己创建自己的唯一实例。单例类必须给所有其他对象提供这一实例。意图保证一个类仅有一个实例并提供一个访问它的全局访问点。主要解决一个全局使用的类频繁地创建与销毁。何时使用当您想控制实例数目节省系统资源的时候。如何解决判断系统是否已经有这个单例如果有则返回如果没有则创建。关键代码构造函数是私有的。应用实例一个班级只有一个班主任。Windows 是多进程多线程的在操作一个文件的时候就不可避免地出现多个进程或线程同时操作一个文件的现象所以所有文件的处理必须通过唯一的实例来进行。一些设备管理器常常设计为单例模式比如一个电脑有两台打印机在输出的时候就要处理不能两台打印机打印同一个文件。优点在内存里只有一个实例减少了内存的开销尤其是频繁的创建和销毁实例比如管理学院首页页面缓存。避免对资源的多重占用比如写文件操作。缺点没有接口不能继承与单一职责原则冲突一个类应该只关心内部逻辑而不关心外面怎么样来实例化。使用场景要求生产唯一序列号。WEB 中的计数器不用每次刷新都在数据库里加一次用单例先缓存起来。创建的一个对象需要消耗的资源过多比如 I/O 与数据库的连接等。注意事项getInstance() 方法中需要使用同步锁 synchronized (Singleton.class) 防止多线程同时进入造成 instance 被多次实例化。单例设计模式分类两种饿汉式类加载就会导致该单实例对象被创建懒汉式类加载不会导致该单实例对象被创建而是首次使用该对象时才会创建2、饿汉式饿汉式在类加载的过程导致该单实例对象被创建虚拟机会保证类加载的线程安全但是如果只是为了加载该类不需要实例则会造成内存的浪费静态变量的方式publicfinalclassSingleton{// 私有构造方法privateSingleton(){}// 在成员位置创建该类的对象privatestaticfinalSingletoninstancenewSingleton();// 对外提供静态方法获取该对象publicstaticSingletongetInstance(){returninstance;}// 解决序列化问题protectedObjectreadResolve(){returnINSTANCE;}}加 final 修饰所以不会被子类继承防止子类中不适当的行为覆盖父类的方法破坏了单例防止反序列化破坏单例的方式对单例声明 transient然后实现 readObject(ObjectInputStream in) 方法复用原来的单例条件访问权限为 private/protected、返回值必须是 Object、异常可以不抛实现 readResolve() 方法当 JVM 从内存中反序列化地组装一个新对象就会自动调用 readResolve 方法返回原来单例构造方法设置为私有防止其他类无限创建对象但是不能防止反射破坏静态变量初始化在类加载时完成由 JVM 保证线程安全能保证单例对象创建时的安全提供静态方法而不是直接将 INSTANCE 设置为 public体现了更好的封装性、提供泛型支持、可以改进成懒汉单例设计静态代码块的方式publicclassSingleton{// 私有构造方法privateSingleton(){}// 在成员位置创建该类的对象privatestaticSingletoninstance;static{instancenewSingleton();}// 对外提供静态方法获取该对象publicstaticSingletongetInstance(){returninstance;}}枚举方式枚举类型是所用单例实现中唯一一种不会被破坏的单例实现模式publicenumSingleton{INSTANCE;publicvoiddoSomething(){System.out.println(doSomething);}}publicstaticvoidmain(String[]args){Singleton.INSTANCE.doSomething();}问题1枚举单例是如何限制实例个数的每个枚举项都是一个实例是一个静态成员变量问题2枚举单例在创建时是否有并发问题否问题3枚举单例能否被反射破坏单例否反射创建对象时判断是枚举类型就直接抛出异常问题4枚举单例能否被反序列化破坏单例否问题5枚举单例属于懒汉式还是饿汉式饿汉式问题6枚举单例如果希望加入一些单例创建时的初始化逻辑该如何做添加构造方法反编译结果publicfinalclassSingletonextendsjava.lang.EnumSingleton{// Enum实现序列化接口publicstaticfinalSingletonINSTANCEnewSingleton();}3、懒汉式线程不安全publicclassSingleton{// 私有构造方法privateSingleton(){}// 在成员位置创建该类的对象privatestaticSingletoninstance;// 对外提供静态方法获取该对象publicstaticSingletongetInstance(){if(instancenull){// 多线程环境会出现线程安全问题可能多个线程同时进入这里instancenewSingleton();}returninstance;}}双端检锁机制在多线程的情况下可能会出现空指针问题出现问题的原因是 JVM 在实例化对象的时候会进行优化和指令重排序操作所以需要使用volatile关键字publicclassSingleton{// 私有构造方法privateSingleton(){}privatestaticvolatileSingletoninstance;// 对外提供静态方法获取该对象publicstaticSingletongetInstance(){// 第一次判断如果instance不为null不进入抢锁阶段直接返回实例if(instancenull){synchronized(Singleton.class){// 抢到锁之后再次判断是否为nullif(instancenull){instancenewSingleton();}}}returninstance;}}静态内部类方式publicclassSingleton{// 私有构造方法privateSingleton(){}privatestaticclassSingletonHolder{privatestaticfinalSingletonINSTANCEnewSingleton();}// 对外提供静态方法获取该对象publicstaticSingletongetInstance(){returnSingletonHolder.INSTANCE;}}内部类属于懒汉式类加载本身就是懒惰的首次调用时加载然后对单例进行初始化类加载的时候方法不会被调用所以不会触发 getInstance 方法调用 invokestatic 指令对内部类进行加载加载的时候字节码常量池会被加入类的运行时常量池解析工作是将常量池中的符号引用解析成直接引用但是解析过程不一定非得在类加载时完成可以延迟到运行时进行所以静态内部类实现单例会延迟加载没有线程安全问题静态变量初始化在类加载时完成由 JVM 保证线程安全4、破坏单例反序列化将单例对象序列化再反序列化对象从内存反序列化到程序中会重新创建一个对象通过反序列化得到的对象是不同的对象而且得到的对象不是通过构造器得到的反序列化得到的对象不执行构造器SingletonpublicclassSingletonimplementsSerializable{//实现序列化接口// 私有构造方法privateSingleton(){}privatestaticclassSingletonHolder{privatestaticfinalSingletonINSTANCEnewSingleton();}// 对外提供静态方法获取该对象publicstaticSingletongetInstance(){returnSingletonHolder.INSTANCE;}}序列化publicclassTest{publicstaticvoidmain(String[]args)throwsException{//往文件中写对象//writeObject2File();//从文件中读取对象Singletons1readObjectFromFile();Singletons2readObjectFromFile();//判断两个反序列化后的对象是否是同一个对象System.out.println(s1s2);}privatestaticSingletonreadObjectFromFile()throwsException{//创建对象输入流对象ObjectInputStreamoisnewObjectInputStream(newFileInputStream(C://a.txt));//第一个读取Singleton对象Singletoninstance(Singleton)ois.readObject();returninstance;}publicstaticvoidwriteObject2File()throwsException{//获取Singleton类的对象SingletoninstanceSingleton.getInstance();//创建对象输出流ObjectOutputStreamoosnewObjectOutputStream(newFileOutputStream(C://a.txt));//将instance对象写出到文件中oos.writeObject(instance);}}解决方法在 Singleton 类中添加readResolve()方法在反序列化时被反射调用如果定义了这个方法就返回这个方法的值如果没有定义则返回新创建的对象privateObjectreadResolve(){returnSingletonHolder.INSTANCE;}ObjectInputStream 类源码分析publicfinalObjectreadObject()throwsIOException,ClassNotFoundException{//...ObjectobjreadObject0(false);//重点查看readObject0方法}privateObjectreadObject0(booleanunshared)throwsIOException{try{switch(tc){caseTC_OBJECT:returncheckResolve(readOrdinaryObject(unshared));}}}privateObjectreadOrdinaryObject(booleanunshared)throwsIOException{// isInstantiable 返回true执行 desc.newInstance()通过反射创建新的单例类objdesc.isInstantiable()?desc.newInstance():null;// 添加 readResolve 方法后 desc.hasReadResolveMethod() 方法执行结果为trueif(obj!nullhandles.lookupException(passHandle)nulldesc.hasReadResolveMethod()){// 通过反射调用 Singleton 类中的 readResolve 方法将返回值赋值给rep变量// 多次调用ObjectInputStream类中的readObject方法本质调用定义的readResolve方法返回的是同一个对象。Objectrepdesc.invokeReadResolve(obj);}returnobj;}反射破解反射publicclassTest{publicstaticvoidmain(String[]args)throwsException{//获取Singleton类的字节码对象ClassclazzSingleton.class;//获取Singleton类的私有无参构造方法对象Constructorconstructorclazz.getDeclaredConstructor();//取消访问检查constructor.setAccessible(true);//创建Singleton类的对象s1Singletons1(Singleton)constructor.newInstance();//创建Singleton类的对象s2Singletons2(Singleton)constructor.newInstance();//判断通过反射创建的两个Singleton对象是否是同一个对象System.out.println(s1s2);//false}}反射方式破解单例的解决方法publicclassSingleton{privatestaticvolatileSingletoninstance;// 私有构造方法privateSingleton(){// 反射破解单例模式需要添加的代码if(instance!null){thrownewRuntimeException();}}// 对外提供静态方法获取该对象publicstaticSingletongetInstance(){if(instance!null){returninstance;}synchronized(Singleton.class){if(instance!null){returninstance;}instancenewSingleton();returninstance;}}}RuntimeRuntime 类就是使用的单例设计模式中的饿汉式publicclassRuntime{privatestaticRuntimecurrentRuntimenewRuntime();publicstaticRuntimegetRuntime(){returncurrentRuntime;}privateRuntime(){}...}使用 RuntimepublicclassRuntimeDemo{publicstaticvoidmain(String[]args)throwsIOException{//获取Runtime类对象RuntimeruntimeRuntime.getRuntime();//返回 Java 虚拟机中的内存总量。System.out.println(runtime.totalMemory());//返回 Java 虚拟机试图使用的最大内存量。System.out.println(runtime.maxMemory());//创建一个新的进程执行指定的字符串命令返回进程对象Processprocessruntime.exec(ipconfig);//获取命令执行后的结果通过输入流获取InputStreaminputStreamprocess.getInputStream();byte[]arrnewbyte[1024*1024*100];intbinputStream.read(arr);System.out.println(newString(arr,0,b,gbk));}}最近看到一个很扎心的现象企业越来越关注开发效率而 AI 正在成为新的生产力工具。同样的需求会使用 AI 的工程师往往能够更快完成设计、编码和测试工作。与其担心被 AI 替代不如尽早学会驾驭 AI。最近我不仅在学习 Java 底层还在学习一些人工智能的知识发现了一个不错的 AI 学习网站内容通俗易懂比较适合程序员快速上手感兴趣的话也可以看看人工智能学习网本篇文章到这里就结束了最后送大家一句话白驹过隙沧海桑田