设计模式之美
王争
前 Google 工程师,《数据结构与算法之美》专栏作者
123425 人已学习
新⼈⾸单¥98
登录后,你可以任选6讲全文学习
课程目录
已完结/共 113 讲
设计模式与范式:行为型 (18讲)
设计模式之美
15
15
1.0x
00:00/00:00
登录|注册

41 | 单例模式(上):为什么说支持懒加载的双重检测不比饿汉式更优?

通过Java枚举类型本身的特性,保证了实例创建的线程安全性和实例的唯一性
最简单的实现方式
实现简单
支持延迟加载,支持高并发
解决了懒汉式并发度低的问题
支持延迟加载、支持高并发的单例实现方式
频繁加锁、释放锁,以及并发度低等问题
支持延迟加载
不支持延迟加载实例
在类加载的期间,就已经将instance静态实例初始化好了
第一个实战案例中,除了类级别锁、分布式锁、并发队列、单例模式等解决方案之外,还有一种非常简单的解决日志互相覆盖问题的方法
在熟悉的编程语言的类库中,有哪些类是单例类?为什么要设计成单例类呢?
枚举
静态内部类
双重检测
懒汉式
饿汉式
解决资源访问冲突的问题
有些数据在系统中只应该保存一份,适合设计为单例类
一个类只允许创建一个对象(或者叫实例),那这个类就是一个单例类
课堂讨论
单例的实现
单例的用处
单例的定义
单例模式

该思维导图由 AI 生成,仅供参考

从今天开始,我们正式进入到设计模式的学习。我们知道,经典的设计模式有 23 种。其中,常用的并不是很多。据我的工作经验来看,常用的可能都不到一半。如果随便抓一个程序员,让他说一说最熟悉的 3 种设计模式,那其中肯定会包含今天要讲的单例模式。
网上有很多讲解单例模式的文章,但大部分都侧重讲解,如何来实现一个线程安全的单例。我今天也会讲到各种单例的实现方法,但是,这并不是我们专栏学习的重点,我重点还是希望带你搞清楚下面这样几个问题(第一个问题会在今天讲解,后面三个问题放到下一节课中讲解)。
为什么要使用单例?
单例存在哪些问题?
单例与静态类的区别?
有何替代的解决方案?
话不多说,让我们带着这些问题,正式开始今天的学习吧!

为什么要使用单例?

单例设计模式(Singleton Design Pattern)理解起来非常简单。一个类只允许创建一个对象(或者实例),那这个类就是一个单例类,这种设计模式就叫作单例设计模式,简称单例模式。
对于单例的概念,我觉得没必要解释太多,你一看就能明白。我们重点看一下,为什么我们需要单例这种设计模式?它能解决哪些问题?接下来我通过两个实战案例来讲解。

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

我们先来看第一个例子。在这个例子中,我们自定义实现了一个往文件中打印日志的 Logger 类。具体的代码实现如下所示:
确认放弃笔记?
放弃后所记笔记将不保留。
新功能上线,你的历史笔记已初始化为私密笔记,是否一键批量公开?
批量公开的笔记不会为你同步至部落
公开
同步至部落
取消
完成
0/2000
荧光笔
直线
曲线
笔记
复制
AI
  • 深入了解
  • 翻译
    • 英语
    • 中文简体
    • 中文繁体
    • 法语
    • 德语
    • 日语
    • 韩语
    • 俄语
    • 西班牙语
    • 阿拉伯语
  • 解释
  • 总结

单例模式是一种常用的设计模式,它保证一个类只能创建一个实例。本文通过实例和讲解深入浅出地介绍了单例模式的应用和实现方式。文章首先介绍了为什么需要使用单例模式,通过实战案例展示了单例模式的应用场景和解决问题的能力。其中,通过Logger类的例子说明了在多线程环境下,使用单例模式可以避免资源访问冲突的问题。另外,通过IdGenerator类的例子展示了单例模式在表示全局唯一类时的应用。文章还介绍了如何实现一个单例,包括构造函数的私有访问权限、线程安全、延迟加载和性能高效等方面的考虑。单例的实现方式包括饿汉式、懒汉式、双重检测、静态内部类和枚举。每种实现方式都有其优势和适用场景,读者可以根据具体需求选择合适的方式。总的来说,本文通过实例和讲解,对读者理解和掌握单例模式具有一定的指导意义。

仅可试看部分内容,如需阅读全部内容,请付费购买文章所属专栏
《设计模式之美》
新⼈⾸单¥98
立即购买
登录 后留言

全部留言(134)

  • 最新
  • 精选
  • 西南偏北
    置顶
    这真的是看过的关于讲单例的最好的文章
    2020-02-05
    13
    329
  • KK
    饿汉式和懒汉式的名字为什么这么起呀?可以解释一下吗?

    作者回复: 着急吃-》饿汉式 不着急吃-》懒汉式

    2020-04-05
    3
    8
  • 星之所在
    争哥我想细问下,我用一个静态变量也可以实现单例的效果,为啥还要用单例设计模式?是为了代码后续扩展性,还是静态变量用多了影响整个代码?

    作者回复: 静态变量没法替代单例啊。单例是类本身不允许多个实例。但是静态变量,我可以定义多个啊,这个怎么解决呢?

    2020-06-10
    6
  • _Walker
    最后枚举那个解释有些含糊其辞呀😂要是能详细解释一下就好了

    作者回复: 不是重点,你自己研究研究吧😂

    2020-05-05
    3
  • 西柚
    老师讲的太好了,逻辑清晰、缜密。思考问题的方式非常值得学习~

    作者回复: 信小争哥就对了

    2020-06-08
    2
  • skying
    争哥,你好! 我这边想模拟出 你文章中 的Logger类写入文件会重复的场景。 但没复现出来, 不知道你这边有 样例代码没有。

    作者回复: 比较难模拟出来的,因为毕竟要并发极端情况下(有竞争的情况下,就像我画的那张图一样)才会发生覆盖的情况。我的样例都在文章里了。

    2020-07-11
    2
    1
  • 子夜2104
    我们现在用的高版本的 Java 已经在 JDK 内部实现中解决了这个问题(解决的方法很简单,只要把对象 new 操作和初始化操作设计为原子操作,就自然能禁止重排序)。 老师,请问是哪个版本解决了这个问题呢?

    作者回复: 好像jdk5之后就解决了,我有点记不清了😂

    2020-05-19
    8
    1
  • 鹤涵
    1. Spring中的一些连接工厂类,Service类都默认是单例模式 这些对象是一般消耗资源或者类似于工具类没有共享变量竞争问题。 2. FileWritter设计成 static final的可以使用jvm类加载特性解决竞争问题。但是可测试性变差

    作者回复: 嗯嗯 ������

    2020-11-26
  • 西门吹牛
    其实有一点不太理解,希望老师解答。 双层检查,加volatile,根据java内存模型,volatile保证的是可见性,也就是说,给变量赋值的操作,会被另一线程看到,这样,另一线程拿到的还是地址,这时候,内存一定初始化完成了吗? 是不是可以理解为,这个volatile 的写操作 happen-before 与后续的读操作,就相当于是,从语言层面考虑,而不是指令层面的。 如果从指令层面考虑,这个new操作,会有三条指令,赋值完成就相当与写操作完成,对象还没初始化完成,这时候别的线程读到还没初始化的地址值会报空指针异常; 如果从语言层面考虑,这个new语句,相当于写操作完成,就代表这个操作对应的所有指令都完成了,所以后续能读到已经初始化的值。 这个happen-before规则是从语言层面考虑还是指令层面?

    作者回复: 我建议你去看下极客的并发专栏😂

    2020-07-01
    2
  • 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-05
    42
    116
收起评论
显示
设置
留言
99+
收藏
沉浸
阅读
分享
手机端
快捷键
回顶部