41 | 单例模式(上):为什么说支持懒加载的双重检测不比饿汉式更优?
王争
该思维导图由 AI 生成,仅供参考
从今天开始,我们正式进入到设计模式的学习。我们知道,经典的设计模式有 23 种。其中,常用的并不是很多。据我的工作经验来看,常用的可能都不到一半。如果随便抓一个程序员,让他说一说最熟悉的 3 种设计模式,那其中肯定会包含今天要讲的单例模式。
网上有很多讲解单例模式的文章,但大部分都侧重讲解,如何来实现一个线程安全的单例。我今天也会讲到各种单例的实现方法,但是,这并不是我们专栏学习的重点,我重点还是希望带你搞清楚下面这样几个问题(第一个问题会在今天讲解,后面三个问题放到下一节课中讲解)。
为什么要使用单例?
单例存在哪些问题?
单例与静态类的区别?
有何替代的解决方案?
话不多说,让我们带着这些问题,正式开始今天的学习吧!
为什么要使用单例?
单例设计模式(Singleton Design Pattern)理解起来非常简单。一个类只允许创建一个对象(或者实例),那这个类就是一个单例类,这种设计模式就叫作单例设计模式,简称单例模式。
对于单例的概念,我觉得没必要解释太多,你一看就能明白。我们重点看一下,为什么我们需要单例这种设计模式?它能解决哪些问题?接下来我通过两个实战案例来讲解。
实战案例一:处理资源访问冲突
我们先来看第一个例子。在这个例子中,我们自定义实现了一个往文件中打印日志的 Logger 类。具体的代码实现如下所示:
公开
同步至部落
取消
完成
0/2000
荧光笔
直线
曲线
笔记
复制
AI
- 深入了解
- 翻译
- 解释
- 总结
单例模式是一种常用的设计模式,它保证一个类只能创建一个实例。本文通过实例和讲解深入浅出地介绍了单例模式的应用和实现方式。文章首先介绍了为什么需要使用单例模式,通过实战案例展示了单例模式的应用场景和解决问题的能力。其中,通过Logger类的例子说明了在多线程环境下,使用单例模式可以避免资源访问冲突的问题。另外,通过IdGenerator类的例子展示了单例模式在表示全局唯一类时的应用。文章还介绍了如何实现一个单例,包括构造函数的私有访问权限、线程安全、延迟加载和性能高效等方面的考虑。单例的实现方式包括饿汉式、懒汉式、双重检测、静态内部类和枚举。每种实现方式都有其优势和适用场景,读者可以根据具体需求选择合适的方式。总的来说,本文通过实例和讲解,对读者理解和掌握单例模式具有一定的指导意义。
仅可试看部分内容,如需阅读全部内容,请付费购买文章所属专栏
《设计模式之美》,新⼈⾸单¥98
《设计模式之美》,新⼈⾸单¥98
立即购买
© 版权归极客邦科技所有,未经许可不得传播售卖。 页面已增加防盗追踪,如有侵权极客邦将依法追究其法律责任。
登录 后留言
全部留言(134)
- 最新
- 精选
- 西南偏北置顶这真的是看过的关于讲单例的最好的文章2020-02-0513329
- KK饿汉式和懒汉式的名字为什么这么起呀?可以解释一下吗?
作者回复: 着急吃-》饿汉式 不着急吃-》懒汉式
2020-04-0538 - 星之所在争哥我想细问下,我用一个静态变量也可以实现单例的效果,为啥还要用单例设计模式?是为了代码后续扩展性,还是静态变量用多了影响整个代码?
作者回复: 静态变量没法替代单例啊。单例是类本身不允许多个实例。但是静态变量,我可以定义多个啊,这个怎么解决呢?
2020-06-106 - _Walker最后枚举那个解释有些含糊其辞呀😂要是能详细解释一下就好了
作者回复: 不是重点,你自己研究研究吧😂
2020-05-053 - 西柚老师讲的太好了,逻辑清晰、缜密。思考问题的方式非常值得学习~
作者回复: 信小争哥就对了
2020-06-082 - skying争哥,你好! 我这边想模拟出 你文章中 的Logger类写入文件会重复的场景。 但没复现出来, 不知道你这边有 样例代码没有。
作者回复: 比较难模拟出来的,因为毕竟要并发极端情况下(有竞争的情况下,就像我画的那张图一样)才会发生覆盖的情况。我的样例都在文章里了。
2020-07-1121 - 子夜2104我们现在用的高版本的 Java 已经在 JDK 内部实现中解决了这个问题(解决的方法很简单,只要把对象 new 操作和初始化操作设计为原子操作,就自然能禁止重排序)。 老师,请问是哪个版本解决了这个问题呢?
作者回复: 好像jdk5之后就解决了,我有点记不清了😂
2020-05-1981 - 鹤涵1. Spring中的一些连接工厂类,Service类都默认是单例模式 这些对象是一般消耗资源或者类似于工具类没有共享变量竞争问题。 2. FileWritter设计成 static final的可以使用jvm类加载特性解决竞争问题。但是可测试性变差
作者回复: 嗯嗯 ������
2020-11-26 - 西门吹牛其实有一点不太理解,希望老师解答。 双层检查,加volatile,根据java内存模型,volatile保证的是可见性,也就是说,给变量赋值的操作,会被另一线程看到,这样,另一线程拿到的还是地址,这时候,内存一定初始化完成了吗? 是不是可以理解为,这个volatile 的写操作 happen-before 与后续的读操作,就相当于是,从语言层面考虑,而不是指令层面的。 如果从指令层面考虑,这个new操作,会有三条指令,赋值完成就相当与写操作完成,对象还没初始化完成,这时候别的线程读到还没初始化的地址值会报空指针异常; 如果从语言层面考虑,这个new语句,相当于写操作完成,就代表这个操作对应的所有指令都完成了,所以后续能读到已经初始化的值。 这个happen-before规则是从语言层面考虑还是指令层面?
作者回复: 我建议你去看下极客的并发专栏😂
2020-07-012 - Douglas争哥新年好, 有个问题想请教一下,单例的实现中看到过一种实现方式,包括在spring源码中有类似的实现 ,代码如下 1. public class Singleton { private static volatile Singleton instance=null; private Singleton() { } public static Singleton getInstance() {// Singleton temp=instance; // 为什么要用局部变量来接收 if (null == temp) { synchronized (Singleton.class) { temp=instance; if (null == temp) { temp=new Singleton(); instance=temp; } } } return instance; } } spring源码 如 ReactiveAdapterRegistry。 JDK 源码 如 AbstractQueuedSynchronizer。 很多地方 都有用 局部变量 来接收 静态的成员变量, 请问下 这么写有什么性能上的优化点吗? jcu 包下面类似的用法太多。想弄明白为什么要这样写 2. 看jdk 官方的文档(JMM)有说明 指令重排发生的地方有很多 ,编译器,及时编译,CPU在硬件层面的优化,看spring 比较新的代码也使用volatile来修饰,你说的new 关键字和初始化 作为原子操作 可以说一下 大概的jdk版本吗2020-02-0542116
收起评论