《设计模式之美》单例模式(上):为什么说支持懒加载的双重检测不比饿汉式更优?

王争《设计模式之美》学习笔记

为什么要使用单例?

  • 单例设计模式(Singleton Design Pattern):一个类只允许创建一个对象(或者实例),那这个类就是一个单例类,这种设计模式就叫作单例设计模式,简称单例模式。

实战案例一:处理资源访问冲突

文中样例
  • 一个往文件中打印日志的 Logger 类。
  • 构造方法中,打开日志文件 log.txt,将写入文件句柄赋值给属性 writer。
  • log() 方法中调用 writer.write() 写入日志内容。
  • 使用此 Logger 类时,首先实例化 new Logger(),然后调用log() 方法写入日志。
样例中问题及解决方案
  • Logger 类中的日志都写到同一个文件 log.txt 中,在多线程环境下,当 Logger 类被多处调用者实例化后,大家同时写 log.txt,可能会存在互相覆盖。
  • 首先我们想到的是加锁方案,在 log() 方法中调用 writer.write() 前加入一个对象锁。但是在不同线程下,不同对象调用并不会共享一把锁,问题依然存在。
进一步解决方案
  • 换成类级别锁,让所有对象都共享一把锁。 具体方案,在 log() 方法中调用 writer.write() 前加入一个类锁。
  • 分布式锁也是一种解决方案,不过,实现一个安全可靠、无bug、高性能的分布式锁,并不是件容易的事情。
  • 并发队列解决方案,多个线程同时往并发队列写日志,一个单独线程将并发队列中数据写入日志文件,也是一个稍复杂的方案。
单例模式解决方案
  • 增加 getInstance() 方法,此方法中返回属性 instance。
  • 而 private static final Logger instance = new Logger()。
  • 调用的时候直接 Logger.getInstance().log(),这样 Logger 类就只会被实例化一次。
  • 好处是可以不用创建那么多 Logger 对象,一方面节省内存空间,另一方面节省系统文件句柄。

实战案例二:表示全局唯一类

  • 业务场景:
    • 有些数据在系统中只应保存一份,比如配置信息类。
    • 唯一递增ID号码生成器,不能生成重复的ID。
  • 文中样例:
    • IdGenerator 类中 getInstance() 方法返回 instance。
    • 而 private static final IdGenerator instance = new IdGenerator()。
    • 获取 id 方法 getId() 中,返回 id.incrementAndGet()。
    • 使用的时候,直接 IdGenerator.getInstance().getId()。

如何实现一个单例?

实现一个单例关注点:

  • 构造函数需要是 private 访问权限的,这样才能避免外部通过 new 创建实例。
  • 考虑对象创建时的线程安全问题.
  • 考虑是否支持延迟加载.
  • 考虑 getInstance() 性能是否高(是否加锁)。

饿汉式

  • 加载的时候,instance 静态实例就已经创建并初始化好了,所以,instance 实例的创建过程是线程安全的。这样的实现方式不支持延迟加载。
  • 有的人认为,提前初始化实例是一种浪费资源的行为,最好的方法应该在用到的时候再去初始化。理由是实例占用资源多或者初始化耗时长:但是如果初始化耗时长,那我们更不能等到要用它的时候再初始化,会影响到系统的性能。如果实例占用资源多,那我们希望在程序启动的时候就能触发报错。
  • 我们前文的例子都是饿汉式。

懒汉式

  • 懒汉式相对于饿汉式的优势是支持延迟加载。
  • 以前文中的 IdGenerator 类举例,在 getInstance() 方法中才会 new IdGenerator() 赋值给属性 instance。
  • 不过这样的缺点显而易见,我们给 getInstance() 方法加了锁,导致并发度很低,相当于串行。如果这个单例类会被频繁用到,那么频繁加锁、释放锁会降低并发,导致性能瓶颈。

双重检测

  • 在这种实现方式中,只要 instance 被创建之后,即便再调用 getInstance() 函数也不会再进入到加锁逻辑中。即支持延迟加载,又解决并发度低的问题。
  • 我们在 getInstance() 方法中,new IdGenerator() 前加入类级别锁。

静态内部类

  • 一种比双重检测更加简单的实现方法,那就是利用 Java 的静态内部类。
  • 在 IdGenerator 类内部创建一个静态内部类 SingletonHold,在此静态内部类里面添加属性 private static final IdGenerator instance = new IdGenerator()。
  • 当类 IdGenerator 被加载的时候,并不会创建 SingletonHolder 实例对象。只有当调用 getInstance() 方法时,SingletonHolder 才会被加载,这个时候才会创建 instance。instance 的唯一性、创建过程的线程安全性,都由 JVM 来保证。

枚举

  • 一种最简单的实现方式,这种实现方式通过 Java 枚举类型本身的特性,基于枚举类型的单例实现。
  • 在 IdGenerator 类中定义枚举类型 INSTANCE。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值