设计模式--单例模式(Singleton pattern)及应用


单例模式

  • 参考文档:
    • 菜鸟教程单例模式介绍了单例模式的6种实现
    • 该文仅介绍spring的单例模式:spring 的单例模式
    • 介绍原理:Spring的单例模式底层实现
    • 参考书籍:漫谈设计模式:从面向对象开始-刘济华.pdf

1. 单例模式解析

  • 单例所指的就是单个实例,也就是说要保证一个类仅有一个实例。
  • 单例模式有以下的特点:
    • ①单例类只能有一个实例
    • ②单例类必须自己创建自己的唯一实例
    • ③单例类必须给所有其他对象提供这一实例

2. 单例模式示例

  • 以下示例都是在同一个JVM中实现的,在分布式环境下如何保证分布在不同机器上的整个应用只有一个实例是更复杂的话题。

2.1 简单单例模式

  • 为了实现上面的特点,有以下基本实现:
/**
 * 单例模式示例:
 * - 简单单例模式
 */
public class Singleton{
    //变量是私有的,外界无法访问
    private static Singleton singleton = new Singleton();
    //other fields...

    //Singleton类只有一个构造方法,被private修饰的, 客户对象无法创建该类实例。
    private Singleton(){

    }

    //实现全局访问点
    public static Singleton getInstance(){
        return singleton;
    }
    //other motheds...
}
  • 如果该实例需要比较复杂的初始化过程时,可以把这个过程应该写在static{……}代码块中
  • 此实现是线程安全的,当多个线程同时去访问该类的getInstance( ) 方法时,不会初始化多个不同的对象,这是因为,JVM在加载此类时,对于static属性的初始化只能由一个线程执行且仅一次。

2.2 延迟创建的单例模式

  • 上面的单例模式会在类加载的时候就初始化实例,如果这样的单例较多,会使程序初始化效率低下。
  • 为了在类的实例第一次使用时才初始化,我们可以把单例的实例化过程放到getInstance()方法中,而不在加载类时预先创建。
/**
 * 单例模式示例:
 * - 延迟创建的单例模式
 */
public class UnThreadSafeSingleton{
    //变量是私有的,外界无法访问
    private static UnThreadSafeSingleton singleton = null;
    //other fields...

    //UnThreadSafeSingleton类只有一个构造方法,被private修饰的, 客户对象无法创建该类实例。
    private UnThreadSafeSingleton(){

    }

    //实现全局访问点
    public static UnThreadSafeSingleton getInstance(){
        //在这里实现延迟创建,但是下面的三行不是线程安全的,
        //在高并发环境下会生成多个不同的该类实例
        //在没有自动内存回收机制的语言平台中,容易内存泄露
        if(singleton ==null){
            singleton = new UnThreadSafeSingleton();
        }
        return singleton;
    }
    //other motheds...
}

  • 为了解决上面线程安全的问题,我们可以给此方法加synchronized关键字,来保证多线程访问都只会实例化一次。

Double-Check Locking

  • 为了提高并发性能,可以只对上面导致线程不安全的三行代码进行同步
  • 参考:java中volatile关键字的含义
/**
 * 单例模式示例:
 * - 线程安全的延迟创建的单例模式
 */
public class DoubleCheckSingleton{
    //变量是私有的,外界无法访问
    //volatile具有synchronized的可见性特点,也就是说线程能够自动发现volatile变量的最新值。
    //这样,如果instatnce实例化成功,其他线程便能立即发现
    private volatile static DoubleCheckSingleton singleton = null;
    //other fields...

    //DoubleCheckSingleton类只有一个构造方法,被private修饰的, 客户对象无法创建该类实例。
    private DoubleCheckSingleton(){

    }

    //实现全局访问点
    public static DoubleCheckSingleton getInstance(){
        //两次检查是为了防止两个线程都进行到同步块的时候,可能会创建两个实例
        if(singleton ==null){
            synchronized (DoubleCheckSingleton.class){
                if(singleton ==null){ //这里如果不检查还是会生成两个实例
                    singleton = new DoubleCheckSingleton();
                }
        }
        return singleton;
    }
    //other motheds...
}

2.3 Initialization on demond holder

  • 另一种实现延迟加载且线程安全的实例化方法
/**
 * 单例模式示例:
 * - 延迟加载单例模式
 */
public class LazyLoadedSingleton{
    //other fields...

    //LazyLoadedSingleton类只有一个构造方法,被private修饰的, 客户对象无法创建该类实例。
    private LazyLoadedSingleton(){

    }

    private static class LazyHolder{
        //变量是私有的,外界无法访问
        //holds zhe singleton class
        private static LazyLoadedSingleton singleton = new LazyLoadedSingleton();

    }
    /**
     * 只有当第一次调用该方法时,JVM才会加载LazyHolder类,然后才初始化static的singleton
     * 这样即保证了线程安全,又实现了延迟加载
     */
    public static LazyLoadedSingleton getInstance(){
        return LazyHolder.singleton;
    }
    //other motheds...
}

2. spring的单例模式

  • 参考文档:
    • 该文仅介绍spring的单例模式:spring 的单例模式
    • 介绍原理:Spring的单例模式底层实现
    • 对spring中单例模式线程安全的讨:Spring中Singleton模式的线程安全
    • 针对上面的单例模式有以下说法:
      • 延迟加载的单例模式可以称为懒汉式单例
      • 非延迟加载的称为饿汉式单例
    • 饿汉式单例在自己被加载时就将自己实例化,如果从资源利用效率角度来讲,比懒汉式单例类稍差些。但是从速度和反应时间角度来讲,则比懒汉式要稍好些。
  • 解释参考文档上的一句话:由于构造函数是私有的,因此该类不能被继承
    • 参考:There is no default constructor available in xxx错误引发
  • 上面的单例模式都不能被继承,所以spring采用的是另一种单例模式,称为单例注册表

2.1 单例注册表方法

  • Spring对单例的底层实现,到底是饿汉式单例还是懒汉式单例呢?呵呵,都不是。Spring框架对单例的支持是采用单例注册表的方式进行实现的。
    • 以下参考文章分析的源代码应该是spring比较老的版本,我参考现在比较新的4.3.2版本,有些不同,但是原理应该是一样的。下面的源代码是spring-beans-4.3.2-REALEASE版本的。
    • 详情参考:Spring的单例模式底层实现
    • Spring对bean实例的创建是采用单例注册表的方式进行实现的,而这个注册表的缓存是HashMap对象,如果配置文件中的配置信息不要求使用单例,Spring会采用新建实例的方式返回对象实例
public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport implements ConfigurableBeanFactory {
......
 public Object getBean(String name) throws BeansException {
        return this.doGetBean(name, (Class)null, (Object[])null, false);
    }
.....
 protected  T doGetBean(String name, Class requiredType, final Object[] args, boolean typeCheckOnly) throws BeansException {
        //对传入的Bean name稍做处理,防止传入的Bean name名有非法字符(或者做转码)  
        final String beanName = this.transformedBeanName(name);
        Object sharedInstance = this.getSingleton(beanName);
        Object bean;
        //从单例注册表中查找,如果存在,就获得了单例
        if(sharedInstance != null && args == null) {
        ......
         bean = this.getObjectForBeanInstance(sharedInstance, name, beanName, (RootBeanDefinition)null);
        } else {
        ......
        //否则
         try {
                //取得bean的定义
                final RootBeanDefinition ex1 = this.getMergedLocalBeanDefinition(beanName);
                ......
                //如果是单例,做如下处理
                 if(ex1.isSingleton()) {
                    sharedInstance = this.getSingleton(beanName, new ObjectFactory() {
                       ......  });
                     bean = this.getObjectForBeanInstance(sharedInstance, name, beanName, ex1);
                } else if(ex1.isPrototype()) {
                ......
                //如果是prototype。创建一个bean
                 Object var25;
                    try {
                        this.beforePrototypeCreation(beanName);
                        var25 = this.createBean(beanName, ex1, args);
                    } finally {
                        this.afterPrototypeCreation(beanName);
                    }
                    ......
                    bean = this.getObjectForBeanInstance(var25, name, beanName, ex1);
                } else {
                ......
                bean = this.getObjectForBeanInstance(var28, name, beanName, ex1);
                }
            } catch (BeansException var23) {
                this.cleanupAfterBeanCreationFailure(beanName);
                throw var23;
            }
        }
        if(requiredType != null && bean != null && !requiredType.isAssignableFrom(bean.getClass())) {
         ......
        } else {
            return bean;
        }
}