5

源码中的设计模式--单例模式 - 北漂程序员

 2 years ago
source link: https://www.cnblogs.com/teach/p/16127085.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

一、模式入场

单例模式在众多的设计模式中应该是最简单的一个,但是要掌握的点也不少。先看下《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管理了默认就是单例的。

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


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK