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

本贴最后更新于 2640 天前,其中的信息可能已经事过景迁

单例模式

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

/** * 单例模式示例: * - 线程安全的延迟创建的单例模式 */ 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 模式的线程安全
    • 针对上面的单例模式有以下说法:
      • 延迟加载的单例模式可以称为懒汉式单例
      • 非延迟加载的称为饿汉式单例
    • 饿汉式单例在自己被加载时就将自己实例化,如果从资源利用效率角度来讲,比懒汉式单例类稍差些。但是从速度和反应时间角度来讲,则比懒汉式要稍好些。
  • 解释参考文档上的一句话:由于构造函数是私有的,因此该类不能被继承
  • 上面的单例模式都不能被继承,所以 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> T doGetBean(String name, Class<T> requiredType, final Object[] args, boolean typeCheckOnly) throws BeansException { //对传入的Bean name稍做处理,防止传入的Bean name名有非法字符(或者做转码) final String beanName = this.transformedBeanName(name); /** Cache of singleton objects: bean name --> bean instance */ /** * private final Map<String, Object> singletonObjects = new ConcurrentHashMap<String, Object>(256); */ 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; } }
  • 设计模式

    设计模式(Design pattern)代表了最佳的实践,通常被有经验的面向对象的软件开发人员所采用。设计模式是软件开发人员在软件开发过程中面临的一般问题的解决方案。这些解决方案是众多软件开发人员经过相当长的一段时间的试验和错误总结出来的。

    200 引用 • 120 回帖
  • Singleton
    5 引用

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...