• 源码中的设计模式--单例模式


    一、模式入场

    单例模式在众多的设计模式中应该是最简单的一个,但是要掌握的点也不少。先看下《head first 设计模式》中给出的释义,

    单件模式 确保一个类只有一个实例,并提供一个全局访问点。

     下面对这个释义进行逐字解释。单件可以称之为单例其实是一个意思。这个释义给出了单例模式中重要的两点,

    1. 一个类只有一个实例;
    2. 提供一个全局的访问点;

    先说第一条,一个类只有一个实例,在一个系统中会有很多类,如下面的订单类Order

    复制代码
    public class Order {
    
    private String orderId;
    private BigDecimal orderAmount;
    private String orderPerson;
    }
    复制代码

    那么现在就有一个问题,如何保证一个类只有一个实例,最先想到的是强制要求这个系统中的所有开发人员,在开发的时候只能实例一次,一个人实例化了,另一个人就不能实例化,这是一个办法,但是却不可行,因为我要实例化这个类的时候总不能先问下其他人,你们实例化过没有,只有其他人没有实例化的前提下你才可以实例化,而且你还要告知其他人以后谁都不能再实例化Order了,这样是不是太傻了,并且效率也太低了,纯靠人为约定肯定是行不通了。有没有其他的方法呐,答案是有的。

    大家都知道,通常情况下实例化一个类,最简单的方式就是new一个,谁说我没有女朋友,new一个啊。现在我们也new一个Order,但是我们发现任何一个人都可以new,这怎么能保证只有一个实例,那么我把它的构造方法设为私有的,这样你们都不能new了吧,能new的只有一个拉,也就是在Order的内部可以new,这就可以保证一个类只有一个实例了,因为只有在类的内部才可以调用其私有的构造方法,其他地方想调用“对不起,您没有访问权限”。

    好了上面通过把类的构造方法设为私有的,保证了一个类只有一个实例,那么如何才能访问到这个实例呐,假设现在的代码是这样的,

    复制代码
    public class Order {
        private String orderId;
        private BigDecimal orderAmount;
        private String orderPerson;
    
        /**
         * 私有的构造方法
         */
        private Order(){
            
        }
    
        /**
         * 通过私有构造方法生成的唯一实例
         */
        Order order=new Order();
    }
    复制代码

    我们现在就要访问到通过私有构造方法生成的实例order,怎么才能访问到呐?提供一个静态方法,静态方法是类级别的,不依赖于实例,可以通过类名.静态方法名的方式访问,如下

    复制代码
    public class Order {
        private String orderId;
        private BigDecimal orderAmount;
        private String orderPerson;
    
        /**
         * 私有的构造方法
         */
        private Order(){
    
        }
    
        /**
         * 通过私有构造方法生成的唯一实例
         */
        private static Order order=new Order();
    
        /**
         * 全局访问点,静态方法
         * @return
         */
        public static Order getInstance(){
            return order;
        }
    }
    复制代码

    通过提供一个静态方法,由静态方法返回该唯一实例,由于静态方法中要引用order实例,所以该实例也必须是静态的,静态方法是公共的,那么order也应设为私有的,这样就提供了一个全局的访问点,任何地方想使用这个唯一实例调用该静态方法即可。

     好了,到目前为止你已经掌握了一些单例模式的方法。

    二、深入单例模式

    一般情况下,单例模式分为懒汉和饿汉两种模式,这两种模式很容易记混,我这里有一个好的记忆方式,下面会提到。

    上面的演示中其实就是饿汉模式,下面看懒汉模式,

    复制代码
    public class Singleton {
        
        private static Singleton singleton;
    
        /**
         * 全局访问点,提供singleton实例的唯一访问
         * @return
         */
        public static Singleton getInstance(){
            if(singleton==null){
                singleton=new Singleton();
            }
            return singleton;
        }
    
        /**
         * 唯一的私有构造函数,提供唯一的实例
         */
        private Singleton(){
            
        }
        
    }
    复制代码

    上面便是懒汉模式。

    对比饿汉模式和懒汉模式,可以发现其区别在于什么时机调用私有的构造方法生成实例,区分方式是,懒汉模式只有在调用全局访问点的时候才会生成实例,而饿汉模式则在类加载的时候便会生成实例,所以根据生成实例的时机去区分饿汉和懒汉就容易的多了。

    这里想留几个思考问题,

    1. 上面的懒汉模式有问题吗?
    2. 生成实例的方式除了new还有其他方式吗?

    三、追寻源码

     在这个模块中想通过源码来学习下单例模式,让大家看看优秀的人是怎么使用单例模式的。

    1、ErrorContext

    在经常使用的mybatis的源码中有ErrorContext这样的一个类,下面贴出ErrorContext中的部分代码

    复制代码
    package org.apache.ibatis.executor;
    
    /**
     * @author Clinton Begin
     */
    public class ErrorContext {
    
      private static final String LINE_SEPARATOR = System.getProperty("line.separator","\n");
      private static final ThreadLocal<ErrorContext> LOCAL = new ThreadLocal<>();
    
      private ErrorContext stored;
      private String resource;
      private String activity;
      private String object;
      private String message;
      private String sql;
      private Throwable cause;
    
    //私有构造方法
    private ErrorContext() { } //全局访问点 public static ErrorContext instance() { ErrorContext context = LOCAL.get(); if (context == null) { context = new ErrorContext(); LOCAL.set(context); } return context; } }
    复制代码

    在上面的代码中,ErrorContext有私有的构造方法,同时具有instance()方法提供全局唯一访问点,而且从方法我们知道这应该是一个懒汉模式。

    再看下instance()方法,细心的小伙伴会说,这个不是全局唯一访问点,这是从Local变量中取的ErrorContext对象,而Local是ThreadLocal级别的,不是整个系统只有一份啊,这里我要说,大家不必局限于字眼,我们也可以把ThreadLocal看成是一个系统啊,它毕竟是属于线程级别的啊,要真正掌握的是单例的本质,可以仔细体会下。

    2、LogFactory

    同样是在mybatis的源码中有LogFactory类,局部代码如下,

    复制代码
    public final class LogFactory {
    
      /**
       * Marker to be used by logging implementations that support markers
       */
      public static final String MARKER = "MYBATIS";
    
      private static Constructor<? extends Log> logConstructor;
    
      static {
        tryImplementation(LogFactory::useSlf4jLogging);
        tryImplementation(LogFactory::useCommonsLogging);
        tryImplementation(LogFactory::useLog4J2Logging);
        tryImplementation(LogFactory::useLog4JLogging);
        tryImplementation(LogFactory::useJdkLogging);
        tryImplementation(LogFactory::useNoLogging);
      }
    //私有构造方法
      private LogFactory() {
        // disable construction
      }
    }
    复制代码

    在该类中可以看到有私有方法,但是却没有提供全局的访问入口,您会说这也是单例模式吗,我说算,这个类符合单例的定义啊,具有私有构造方法肯定只有一个实例,但是却没有创建实例,这个类中其他的方法均是工具方法,为什么不提供全局访问入口,答案是用不到,用不到所以就不提供了啊。

    3、单例bean

    现在开发中用的最多的就是springboot,springboot的基础是spring,把类交给spring管理使用@Autowired就搞定了,您是不是也知道spring中的bean默认都是单例的,没错spring中使用了单例模式,有同学就说了,在平时写的类中也没有提供私有的构造方法啊,是如何保证单例的呐,还记得上边的思考问题吗?除了使用new的方式还有其他的方式,spring使用的是反射的方式,具体代码先不贴了,太多了,一时半会分析不明白,那全局的访问方式呐,答案是beanFactory

    在beanFactory中定义了很多getBean的方法,调用这些方法便会返回一个单例bean,那这些单例bean存储在什么地方那,答案在DefaultSingletonBeanRegistry中,该类中有一个singletonObjects属性,该属性中就存着所有spring管理的单例bean,

    老铁们,看到了吧,这也是单例模式,但这个单例模式比平时自己写的单例模式高明多了,在生成唯一实例时使用的是反射,在提供全局的访问入口的时候,是从hashmap中返回的,比自己写个静态方法高明多了。

    有的小伙伴会问,一个类被spring管理也没提供私有方法,是不是可以自己new啊,是可以的,随便new多少个都行,但是只要被spring管理了默认就是单例的。

     

    好了,本次就说这么多,我们下次见!

     

     

  • 相关阅读:
    CSS 文字特效运用目录
    Leetcode 918. Maximum Sum Circular Subarray (单调队列好题)
    FPGA 图像缩放 千兆网 UDP 网络视频传输,基于RTL8211 PHY实现,提供工程和QT上位机源码加技术支持
    华为数通方向HCIP-DataCom H12-831题库(多选题:61-80)
    医学图像匹配,c++计算归一化互相关
    VS2019下生成dll动态库及其引入实验
    【Pandas总结】第六节 Pandas 添加列
    linux配置免密登陆
    产品设计小技能
    系统总出故障怎么办,或许你该学学稳定性建设!
  • 原文地址:https://www.cnblogs.com/teach/p/16127085.html