设计模式之单例模式

    技术2024-01-06  100

    单例模式的优点之为什么要使用单例模式

    1.由于单例模式只生成一个实例,减小系统性能开销,当一个对象的产生需要多个资源时(读取配置文件、产生其他依赖)那么我们可以在应用启动时直接创建该对象实例,然后永久驻留内存。

    2.单例模式可以在系统设置全局访问点,优化共享资源访问。例如可以设计一个单例类,负责所有数据表的映射处理。

    五种单例模式的实现方式

    饿汉式单例模式(线程安全,调用效率高,不能延时加载)

    package singal; /** 饿汉式单例 一上来就把对象加载了 可能会存在浪费内存的问题 **/ public class Hungry { private Byte aByte1[]=new Byte[1024]; private Byte aByte2[]=new Byte[1024]; private Byte aByte3[]=new Byte[1024]; private Byte aByte4[]=new Byte[1024]; //构造函数私有化 private Hungry(){ } private final static Hungry HUNGRY =new Hungry(); //类初始化时立即加载对象! //方法没有同步所以调用效率高 public static Hungry getInstance(){ return HUNGRY; } }

    懒汉式单例模式(调用效率不高、可以延时加载)

    package singal; //懒汉式 public class LazySingal { private LazySingal(){ } private static LazySingal LAZY_SINGAL; 延迟加载,也称为懒加载 真正用的时候我们才加载 public static synchronized LazySingal getInstance(){ //如果不加同步的话会存在创建对象不唯一的情况 if (LAZY_SINGAL==null){ LAZY_SINGAL=new LazySingal(); } return LAZY_SINGAL; } }

    DCL懒汉式单例模式(volatile)(懒汉式以及双重检测锁式)

    package singal; import javax.sound.midi.Soundbank; import java.lang.reflect.Constructor; import java.lang.reflect.Field; /** 懒汉式单例 **/ public class LazyMan { private static Boolean XUAN =false; //自定义布尔常量防止反射创建新对象 //构造函数私有 private LazyMan(){ synchronized (LazyMan.class){ if (XUAN==false){ XUAN=true; }else throw new RuntimeException("不要试图通过反射来破坏单例!"); } } private volatile static LazyMan lazyMan; //延迟加载,也称为懒加载 真正用的时候我们才加载 //双重检测的懒汉式单例 DCL懒汉式 public static LazyMan getInstance(){ if (lazyMan==null){ synchronized (LazyMan.class){ if (lazyMan==null){ lazyMan= new LazyMan(); /** * 为什么要加volatile? * 因为在创建对象的时候 * 1.分配内存空间 * 2.执行构造方法初始化对象 * 3.把这个对象指向内存空间 * 原本顺序为123 但是在内存中是完全有可能发生指令排序出现问题的现象的比如132 这不算错误但是对于线程来说就 * 是会出现问题 * 如果A线程执行132 B线程进来时原本还没完成构造的A内存空间被误判为非空 * 于是B线程直接走return lazyMan 但是这空间却是虚无的 */ } } } return lazyMan; } //测试多线程并发 /*public static void main(String[] args) { for (int i = 0; i < 10; i++) { new Thread(()->{ LazyMan.getInstance(); }).start(); } }*/ //通过反射来破坏单例 public static void main(String[] args) throws Exception { //拿到XUAN字段 Field xuan = LazyMan.class.getDeclaredField("XUAN"); //将private失效 xuan.setAccessible(true); //懒汉式实例对象 //LazyMan instance = LazyMan.getInstance(); //获取空构造 Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null); //使private失效 declaredConstructor.setAccessible(true); //构造实例对象 LazyMan newInstance = declaredConstructor.newInstance(); //set false就是为了能够再创建对象 xuan.set(newInstance,false); LazyMan newInstance1 = declaredConstructor.newInstance(); //对象比较 System.out.println(newInstance1); System.out.println(newInstance); System.out.println(newInstance1==newInstance); } } singal.LazyMan@4e50df2e <<-----------上面的输出结果 明显是单例被反射破坏了 结果产生了两个对象 singal.LazyMan@1d81eb93 false

    静态内部类(线程安全、调用效率高、可以延时加载)

    package singal; // 静态内部类 public class Holder { //构造函数私有化 private Holder(){ } public static Holder getInstance(){ //只有你去调用内部类的时候才会加载初始化对象 return InnerClass.HOLDER; } public static class InnerClass{ private static final Holder HOLDER =new Holder(); } }

    以上都是可以通过反射直接破坏单例模式的

    枚举(反射和反序列化不能破坏的单例)(调用效率高,不能延时加载)

    //由JVM从根本上提供保障 @CallerSensitive @ForceInline // to ensure Reflection.getCallerClass optimization public T newInstance(Object ... initargs) throws InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException { Class<?> caller = override ? null : Reflection.getCallerClass(); return newInstanceWithCaller(initargs, !override, caller); } /* package-private */ T newInstanceWithCaller(Object[] args, boolean checkAccess, Class<?> caller) throws InstantiationException, IllegalAccessException, InvocationTargetException { if (checkAccess) checkAccess(caller, clazz, clazz, modifiers); if ((clazz.getModifiers() & Modifier.ENUM) != 0) //用枚举创建对象 throw new IllegalArgumentException("Cannot reflectively create enum objects");//用反射破坏枚举单例会报错 ConstructorAccessor ca = constructorAccessor; // read volatile if (ca == null) { ca = acquireConstructorAccessor(); } @SuppressWarnings("unchecked") T inst = (T) ca.newInstance(args); return inst; } package singal; import java.lang.reflect.Constructor; /** * 什么是枚举?一个被命名整数常数集合 **/ public enum EnumSingle { //这是一个枚举类 INSTANCE; //单例枚举对象 public EnumSingle getInstance(){ return INSTANCE; } } class Test{ public static void main(String[] args) throws Exception { EnumSingle single1 = EnumSingle.INSTANCE; //EnumSingle single2 = EnumSingle.INSTANCE; //我们尝试反射创建一下对象 发现是不行的 Constructor<EnumSingle> declaredConstructor = EnumSingle.class.getDeclaredConstructor(String.class,int.class);//String.class,int.class是反编译出来构造参数 declaredConstructor.setAccessible(true); EnumSingle single2 = declaredConstructor.newInstance(); System.out.println(single1); System.out.println(single2); } } Exception in thread "main" java.lang.IllegalArgumentException: Cannot reflectively create enum objects at java.base/java.lang.reflect.Constructor.newInstanceWithCaller(Constructor.java:493) at java.base/java.lang.reflect.Constructor.newInstance(Constructor.java:481) at singal.Test.main(EnumSingle.java:28) <<<------ 控制台输出结果

    单例模式的应用场景

    Windows的任务管理器就是典型的单例模式(不论你打开多少次任务管理器 只能打开一个)应用程序的日志应用,因为要实现实时的动态更新,一般只能有一个实例去操作。项目中读取配置文件的类,一般只有一个对象。(Properties加载DataSource)数据库连接池也是采用单例模式spring的IOC容器中的每一个bean都是单例的这样能方便容器管理SpringMVC的控制器也是单例的

    如何选用合适的单例模式?

    单例对象占用资源少,不需要延迟加载 此时枚举好用于饿汉式单例对象占用资源多,需要延时加载 静态内部类好用于懒汉式
    Processed: 0.017, SQL: 9