单例模式
单例模式的意义:
有一些对象我们只需要一个实例,比如说线程池、缓存、对话框、日志对象、重放打印机、显卡等设备的驱动程序。这些对象只能有一个实例,否则会产生很多问题。
为了确保只有一个实例,有时我们通过全局变量的形式实现,但是将对象赋值给全局变量,但是却没有使用就会造成资源的浪费。所以还只实例化一个实例更好。
总所周知,类对象的构造函默认是public类型的,这样我就是说这个对象可以有很多实例。当然我们构造函数虽然不是公有的类型(如protected),只有同一个包的类可以实例化它时,但是仍可以实例化多次,
私有的构造函数不能被类外实例化,只能类内部实例化。但很显然不能够通过类的实例来调用构造器,因为类实例的产生和构造器的调用就像"鸡生蛋,蛋生鸡"一样, 谁先谁后说不清楚。但是如果通过类用却是可以的。可以通过调用该类的静态方法,再通过静态方法调用私有的构造函数。
单例模式确保一个类只有一个实例,并提供一个全局的访问点。
单例模式的种类:
简单懒汉式
一个简单的单例模式的实现如下:
public class LazySingleton { //利用一个静态变量记录LazySingleton的唯一实例 private static LazySingleton uniqueInstance; //把构造器声明为私有的,只有自LazySingleton类内才可以调用构造器 private LazySingleton(){} //用getinstance方法实例化对象,并返回这个实例。 public static LazySingleton singletonGetInstance() { if(uniqueInstance != null){ //需要的时候就产生实例 uniqueInstance = new LazySingleton(); }else{ System.out.println("already exists lazySingleton instance"); } return uniqueInstance; } }
通过懒汉模式获取单例在多线程的情况是不安全的,我们可以通过对获取实例的方式进行加锁。
尽管如此,这种方式只有第一次执行的时侯,才真正需要同步。一旦设置好uniqueInstance这个变量,就不再需要同步这个方法了以后的每次调用都会显得累赘。
升级后的懒汉式
当然如果getInstanced()的性能对应用程序不是很关键,可以什么都不做。但是要考虑加了同步的方法性能可能下降100倍那么getInstance的方法被频繁使用,可能需要慎重考虑了。
当然我们可以把同步的地方换到方法内部,如下所示:
public class UpdatedLazySingleton { //volatile关键词确保,当uniqueInstance变量被初始化成实例时,多个线程正确地处理uniqueInstance变量 private volatile static UpdatedLazySingleton uniqueInstance; private UpdatedLazySingleton(){} public static UpdatedLazySingleton getInstance() { //检查实例,如果不存在则进入同步区块 if(uniqueInstance == null){ //只有第一次才彻底执行这里的代码,当然这里可能有多个线程在这里等待,所以需要在同步块内部再次判断单例变量是否为空。 synchronized (UpdatedLazySingleton.class) { if(uniqueInstance == null){ //进入区块后,再检查一次,如果仍是null,才创建实例 uniqueInstance = new UpdatedLazySingleton(); } } } return uniqueInstance; } }
这种双重if检查加锁的实现方式可以大大减少getInstance()的时间消耗。
饿汉式
如果应用程序总是创建并使用单件实例,或者在创建和运行时方面的负担不太繁重,可能需要急切创建此单例:
public class HungrySingleton { private static HungrySingleton uniqueInstance = new HungrySingleton(); private HungrySingleton(){} public static HungrySingleton getInstance(){ return uniqueInstance; } }
这种做法依赖JVM在加载这个类时马上创建此唯一的单件实例。JVM保证在任何线程访问uniqueInstance静态变量之前,一定先创建此实例。虽然这种做法在性能上比较号,但是如果这个单例没有被使用,那么会占用资源。
静态类优化的饿汉式
饿汉模式可以通过静态内部类进行优化,代码如下:
class StaticInnerSingleton { private static class HolderClass{ //默认是不加载的 private static final StaticInnerSingleton instance = new StaticInnerSingleton(); } public static StaticInnerSingleton getInstance(){ //返回结果之前一定先会先加载内部类 return HolderClass.instance; } }
枚举单例
public class EnumSingleton { private EnumSingleton() { System.out.println("+++++"); } public static EnumSingleton getInstance() { return EnumHolder.INSTANCE.getSingleton(); } //使用枚举充当holder private enum EnumHolder { INSTANCE; private EnumSingleton instance; EnumHolder() { this.instance = new EnumSingleton(); } private EnumSingleton getSingleton() { return instance; } } }
破环单例:
反射破坏单例:
import org.junit.Test; import java.lang.reflect.Constructor; public class ReflectionDestroySingleton { @Test public void destroyHungrySingleton(){ Class<?> clazz = HungrySingleton.class; try { Constructor<?> constructor = clazz.getDeclaredConstructor(); constructor.setAccessible(true); HungrySingleton singleton = (HungrySingleton)constructor.newInstance(); HungrySingleton singleton2 = (HungrySingleton)constructor.newInstance(); System.out.println("反射破坏饿汉模式"); System.out.println(singleton == singleton2); System.out.println(singleton); System.out.println(singleton2); } catch (Exception e) { e.printStackTrace(); } } @Test public void destroyStaticInnerSingleton(){ Class<?> clazz = StaticInnerSingleton.class; try { Constructor<?> constructor = clazz.getDeclaredConstructor(); constructor.setAccessible(true); StaticInnerSingleton singleton = (StaticInnerSingleton)constructor.newInstance(); StaticInnerSingleton singleton2 = (StaticInnerSingleton)constructor.newInstance(); System.out.println("反射破坏静态内部类实现的饿汉模式"); System.out.println(singleton == singleton2); System.out.println(singleton); System.out.println(singleton2); } catch (Exception e) { e.printStackTrace(); } } @Test public void destroyUpdatedLazySingleton(){ Class<?> clazz = UpdatedLazySingleton.class; try { Constructor<?> constructor = clazz.getDeclaredConstructor(); constructor.setAccessible(true); UpdatedLazySingleton singleton = (UpdatedLazySingleton)constructor.newInstance(); UpdatedLazySingleton singleton2 = (UpdatedLazySingleton)constructor.newInstance(); System.out.println("反射破坏升级后的懒汉模式"); System.out.println(singleton == singleton2); System.out.println(singleton); System.out.println(singleton2); } catch (Exception e) { e.printStackTrace(); } } @Test public void destroyLazySingleton(){ Class<?> clazz = LazySingleton.class; try { Constructor<?> constructor = clazz.getDeclaredConstructor(); constructor.setAccessible(true); LazySingleton singleton = (LazySingleton)constructor.newInstance(); LazySingleton singleton2 = (LazySingleton)constructor.newInstance(); System.out.println("反射破坏升级后的懒汉模式"); System.out.println(singleton == singleton2); System.out.println(singleton); System.out.println(singleton2); } catch (Exception e) { e.printStackTrace(); } } @Test public void destroyEnumSingleton(){ Class<?> clazz = EnumSingleton.class; try { Constructor<?> constructor = clazz.getDeclaredConstructor(); constructor.setAccessible(true); EnumSingleton singleton = (EnumSingleton)constructor.newInstance(); EnumSingleton singleton2 = (EnumSingleton)constructor.newInstance(); System.out.println("反射破坏枚举单例"); System.out.println(singleton == singleton2); System.out.println(singleton); System.out.println(singleton2); } catch (Exception e) { e.printStackTrace(); } } }
执行结果:
反射破坏升级后的懒汉模式 false headFirst.singleton.LazySingleton@2b71fc7e headFirst.singleton.LazySingleton@5ce65a89 反射破坏饿汉模式 false headFirst.singleton.HungrySingleton@69d0a921 headFirst.singleton.HungrySingleton@446cdf90 反射破坏静态内部类实现的饿汉模式 false headFirst.singleton.StaticInnerSingleton@4b85612c headFirst.singleton.StaticInnerSingleton@277050dc 反射破坏升级后的懒汉模式 false headFirst.singleton.UpdatedLazySingleton@7aec35a headFirst.singleton.UpdatedLazySingleton@67424e82 反射破坏枚举单例 java.lang.NoSuchMethodException: headFirst.singleton.EnumSingleton.<init>() at java.lang.Class.getConstructor0(Class.java:3082) at java.lang.Class.getDeclaredConstructor(Class.java:2178) at headFirst.singleton.ReflectionDestroySingleton.destroyEnumSingleton(ReflectionDestroySingleton.java:81)
序列化破环单例:
public class SerializableDestroySingleton { @Test public void destroyLazySingleton(){ LazySingleton lazySingleton = null; LazySingleton lazySingleton2 = LazySingleton.getInstance(); FileOutputStream fileOutputStream = null; try { fileOutputStream = new FileOutputStream("LazySingleton.obj"); ObjectOutputStream out = new ObjectOutputStream(fileOutputStream); out.writeObject(lazySingleton2); out.flush(); fileOutputStream.close(); FileInputStream fileInputStream = new FileInputStream("LazySingleton.obj"); ObjectInputStream inputStream = new ObjectInputStream(fileInputStream); Object o = inputStream.readObject(); lazySingleton = (LazySingleton)o; System.out.println(lazySingleton == lazySingleton2); System.out.println(lazySingleton); System.out.println(lazySingleton2); } catch (Exception e) { e.printStackTrace(); } } }
以单例破坏和序列化破坏为例,简述预防措施:
public class LazySingleton implements Serializable{ //利用一个静态变量记录LazySingleton的唯一实例 private static LazySingleton uniqueInstance; //把构造器声明为私有的,只有自LazySingleton类内才可以调用构造器 private LazySingleton() { //防止反射创建不同的单例对象 throw new RuntimeException("非法创建对象"); } //用getinstance方法实例化对象,并返回这个实例。 public static LazySingleton getInstance() { if (uniqueInstance != null) { //需要的时候就产生实例 uniqueInstance = new LazySingleton(); } else { System.out.println("already exists lazySingleton instance"); } return uniqueInstance; } private Object readResolve(){ return uniqueInstance; } }
public class SerializableDestroySingleton {
@Test
public void destroyLazySingleton(){
LazySingleton lazySingleton = null;
LazySingleton lazySingleton2 = LazySingleton.getInstance();
FileOutputStream fileOutputStream = null;
try {
fileOutputStream = new FileOutputStream("LazySingleton.obj");
ObjectOutputStream out = new ObjectOutputStream(fileOutputStream);
out.writeObject(lazySingleton2);
out.flush();
fileOutputStream.close();
FileInputStream fileInputStream = new FileInputStream("LazySingleton.obj");
ObjectInputStream inputStream = new ObjectInputStream(fileInputStream);
Object o = inputStream.readObject();
lazySingleton = (LazySingleton)o;
System.out.println(lazySingleton == lazySingleton2);
System.out.println(lazySingleton);
System.out.println(lazySingleton2);
} catch (Exception e) {
e.printStackTrace();
}
}
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 【杭电多校比赛记录】2025“钉耙编程”中国大学生算法设计春季联赛(1)