• 【设计模式1_单例模式】


    对于重量级、无状态的对象,对象的重复创建和销毁是有巨大开销的,在系统中可以保持唯一实例以支持重复使用。
    受Spring管理的对象默认都是单例,另外自己创建的线程池、连接池也应该保证单例。

    如何创建:私有化构造器,并提供一个全局唯一的访问入口以获取单例对象。

    饿汉式单例模式

    利用类加载机制创建出对象,由jvm保证对象唯一。
    在类加载的 ‘准备’阶段时就创建好了对象,等第一次通过getInstance()方法获取对象时就可以直接拿到,所以叫饿汉。
    缺点是对象过早创建,即使它并不一定会被使用,造成资源浪费。

    /**
     * 饿汉单例模式
     */
    public class EagerSingleton {
     
        //私有化构造器
        private EagerSingleton(){
        }
        
    	// 利用类加载保证只执行一次
    	// 注意final 修饰才能在类加载的 ‘准备’阶段直接赋初始值,也就是直接执行了new EagerSingleton()
        private static final EagerSingleton eagerSingleton = new EagerSingleton(); 
     
     	// 提供获取单例对象的访问
        public static EagerSingleton getInstance(){
            return eagerSingleton;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    懒汉式单例模式

    懒汉单例模式-基础版本
    /**
     * 懒汉单例模式-基础版本
     * 当getInstance()第一次被调用时创建出对象,后续再调用getInstance()时判断对象已有创建,则直接return
     * 
     * 在多线程下,可能会创建出多实例
     */
    public class LazySingleton { 
    
    	private LazySingleton() {}
    
    	private static LazySingleton instance; 
    
    	public static LazySingleton getInstance() {
    		if(instance == null) {
    			instance = new LazySingleton();
    		}
    
    		return instance; //对象已有创建,则直接return
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    懒汉单例模式-双重校验锁
    /**
     * 懒汉单例模式-双重校验锁
     * 第一个if(instance == null) 的作用:
     * 		判断对象已存在则不用加锁,降低锁粒度;
     * 第二个if(instance == null) 的作用:
     * 		仍然可能多个线程都通过了第一个if,未抢到锁的线程将被阻塞,再重新获得synchronized 锁后,
     * 	会继续执行synchronized中的内容,没有第二个if仍将创建出多实例,需要再判断一次。
     * 
     * volatile 的作用:禁止指令重排
     * new LazySingleton()只有一句代码,但在字节码层面是三个步骤:
     * 		1. 分配堆内存空间
     * 		2. 在堆上初始化这个对象
     *  	3. 堆堆引用地址赋值给变量
     * 	  2和3都依赖于1,但2和3之间没有依赖关系,可能发生指令重排序为 1->3->2  当引用赋值后,instance 不再等于 null,
     * 其他线程可直接返回instance ,而实际上第2步,对象初始化并未完成,那么拿到instance 的线程将发生空指针。
     */
    public class LazySingleton { 
    
    	private LazySingleton() {}
    
    	private static volatile LazySingleton instance;  // volatile 禁止指令重排
    
    	public static LazySingleton getInstance() {
    		if(instance == null) {
    			synchronized (LazySingleton.class) {
    				if(instance == null) {
    					instance = new LazySingleton();
    				}
    			}
    		}
    		return instance; //对象已有创建,则直接return
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    静态内部类单例模式

    public class SingleTon{
      private SingleTon(){}
     
      private static class SingleTonHolder{
         private static SingleTon instance = new SingleTon();
     }
     
      public static SingleTon getInstance(){
        return SingleTonHolder.instance;
      }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    结合类加载的知识。
    外部类和内部类一样,经过加载、连接后并不立刻初始化。(此时内部类的instance 只会赋默认值null)

    只有当调用外部类:LazySingleton.getInstance() 并返回调用 内部类 静态instance属性时,触发内部类初始化,为instance 类变量赋初始值。这个初始值就是 new LazySingleton();

    为instance 类变量赋初始值这个过程只会发生一次,且由JVM保证这个过程是单线程的。new LazySingleton() 的过程也是instance 赋初始值的过程,而赋初始值的过程是单线程的,就保证了new 对象期间不会发生doubleCheck中那样线程上下文切换。


    以上的写法都是自己刚好就是需要被保证单例的资源类;但其实也可以是仅仅作为一个util类,对第三方的资源类提供一个获取单例的入口,而不涉及对这个资源类的代码改动。

    利用反射创建出多实例

    以上饿汉、懒汉、内部类创建的单例模式其实不能阻止利用反射创建出多个实例;

        SingleTon st1=  SingleTon.getInstance();
      
    	Constructor<SingleTon> constructor = SingleTon.class.getDeclaredConstructor(); // 获取无参构造器
        constructor.setAccessible(true);  // 改变私有属性
        SingleTon st2= constructor.newInstance();  
        
        System.out.println(st1== st2); // false
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    解决办法是:在私有化的构造器中再加上判断实例如果已存在,则抛出异常RuntimeException

    public class SingleTon{
    
      private SingleTon(){
          if (SingleTonHolder.instance != null){
              throw new RuntimeException("不能创建多个实例");
          }
      }
     
      private static class SingleTonHolder{
         private static SingleTon instance = new SingleTon();
     }
     
      public static SingleTon getInstance(){
        return SingleTonHolder.instance;
      }
    
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    利用反序列化创建出多实例
    
    
    • 1

    防反序列化,需要通过提供readResolve方法,返回instance。

    枚举单例模式

    外部通过 SomeThing.INSTANCE.getInstance() 就可以访问单实例。

    public enum SomeThing {
        INSTANCE;
        
        private Resource instance;
       
        private SomeThing() {
            instance = new Resource();
        }
        public Resource getInstance() {
            return instance;
        }
    }
    
    
    class Resource{
    	// Resource 是我们要单保证例的资源,网络连接,数据库连接,线程池等等...
    }
    
    // 有个疑问。虽然能保证通过SomeThing.getInstance() 拿到的Resource对象是单例,但并不能防止在其他地方直接 new Resource()...
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    枚举类型单例,写在枚举容器,也不会对原资源做改造,可以防被反序列化和反射生成多个实例。

    使用反射declaredConstructor.newInstance()的源码中,会判断如果当前是枚举类型,则抛出异常,也就意味着如果通过枚举创建单例模式可以防止反射创建多实例。

  • 相关阅读:
    TensorFlow入门(一、环境搭建)
    深入理解New操作符
    Redis主从复制
    TexFormula2Word: 将Latex公式转换为MathML的Chrome扩展
    Java设计模式之策略模式
    并行多核体系结构基础 Yan Solihin 第3章 共享存储并行编程 摘录
    算法&&&
    简化 Go 开发:使用强大的工具提高生产力
    swagger stub https无法访问
    法国博士后招聘|国家科学研究中心 (CNRS) 计算生物学
  • 原文地址:https://blog.csdn.net/qq_45494908/article/details/126560105