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

2020-02-05 王争
《设计模式之美》
课程介绍


讲述:冯永吉

时长:大小11.45M

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

为什么要使用单例?

单例设计模式(Singleton Design Pattern)理解起来非常简单。一个类只允许创建一个对象(或者实例),那这个类就是一个单例类,这种设计模式就叫作单例设计模...

展开全文
© 版权归极客邦科技所有,未经许可不得传播售卖。 页面已增加防盗追踪,如有侵权极客邦将依法追究其法律责任。

精选留言

  • aof
    置顶
    2020-02-05
    这真的是看过的关于讲单例的最好的文章
    11
    248
  • Douglas
    2020-02-05
    争哥新年好, 有个问题想请教一下,单例的实现中看到过一种实现方式,包括在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版本吗
    展开
    25
    83
  • 辣么大
    2020-02-05
    简单的方法:创建一个静态私有的filewritter,多线程或者多个Logger对象共享一个filewritter。
    3
    30
  • xindoo
    2020-02-10
    为什么高版本的jdk单例不再需要volatile修饰,求详细参考资料,感谢🙏
    36
    14
  • 写代码的
    2020-08-04
    懒汉式还是饿汉式更好我觉得需要看具体的场景。对于那些短生命周期的应用,如客户端应用来说,启动是频繁发生的,如果启动时导致了一堆饿汉初始化,会给用户带来不好的体验,如果把初始化往后延,将初始化分散在未来的各个时间点,即使某个懒汉初始化时间较长,用户也几乎无感知。而对于生命周期较长的应用,长痛不如短痛,启动时耗点时,保证后面的使用流畅也是可取的。
    
    14
  • 夏目
    2020-04-22
    枚举模式的单例还可以防止序列化和反序列化生成新的实例
    2
    13
  • 守拙
    2020-02-05
    Singleton指仅仅被实例化一次的类. Singleton通常被用来代表本质上唯一的系统组件, 如窗口管理器或文件系统.

    -- <Effective Java> p14

    课堂讨论:

    1. 在你所熟悉的编程语言的类库中,有哪些类是单例类?又为什么要设计成单例类呢?

       Android SDK中的AccessibilityManager应用了懒汉式单例模式.

       

    2. 在第一个实战案例中,除了我们讲到的类级别锁、分布式锁、并发队列、单例模式等解决方案之外,实际上还有一种非常简单的解决日志互相覆盖问题的方法,你想到了吗?

    ​ 将FileWriter的声明用static修饰, 即可解决日志相互覆盖的问题. 原理: 被声明为static的成员变量由类的所有实例所共享, 所以Logger类的所有实例都是通过同一FileWriter写入日志到文件.
    展开
    4
    13
  • Murrre
    2020-04-08
    争哥讲得真好,不过有一点也有疑问.
    ----------------------
    只有很低版本的 Java 才会有这个问题。我们现在用的高版本的 Java 已经在 JDK 内部实现中解决了这个问题(解决的方法很简单,只要把对象 new 操作和初始化操作设计为原子操作,就自然能禁止重排序)。
    ---------------
    这个在google百度都没找到,争哥能提供一下相关资料吗
    展开
    1
    6
  • techwro
    2020-02-15
    争哥,求“只要把对象 new 操作和初始化操作设计为原子操作,就自然能禁止重排序”这一说法的出处。
    4
    6
  • 黄林晴
    2020-02-05
    打卡

    看过Eventbus 的源码,写法是典型的双重锁检验方式,但是构造方法是public 的

    看源码解释,这是因为EventBus可能有多条总线,订阅者注册到不同线上的 EventBus,通过不同的实例来发送数据,不同的 EventBus 是相互隔离开的,订阅者都只会收到注册到该线上事件。

    但是按照单例定义他又不属于单例,感觉很有疑问
    展开
    8
    6
  • 肖臧
    2020-04-13
    90年代的论文的确说了因为JVM的优化和多核CPU会对指令进行reordering,volatile才能解决双检锁问题:http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html
    
    5
  • 李小四
    2020-02-22
    设计模式_41:
    # 作业
    1. Java中有很多使用单例模式,比如`java.lang.Runtime`类,这么设计的原因是,同一个进程中只需要一个实例。
    2. 使用同一个`writer`,比如用`static`修饰。

    # 感想

    1. 初学Java的时候,老师讲构造函数的第二节,先是讲了`private`修饰构造函数后如何获取对象的问题,后来引出了单例模式,感激老师的循序渐进。

    2. 今天的题目虽然是“双重加锁并没有好多少”,但文章中并没有解释,我猜后文中一定会分析,但也逃不过对于饿汉式的思考: 不能延时加载导致了饿汉式在更多时间地占用了资源,但占用资源少并不是软件工程的唯一追求,更好地完成业务才是第一要务,呼应之前的内容,有时候用空间(资源)换时间也很好的策略。
    展开
    
    6
  • zs阿帅
    2020-02-06
    争哥,如果服务是多个实例跑,日志那个单例模式会导致覆盖吗?
    6
    5
  • 大头
    2020-03-17
    枚举实现单例的最佳实践。代码简洁,由jvm保证线程安全和单一实例。还可以有效防止序列化和反序列化造成多个实例和利用反射创建多个实例的情况
    1
    4
  • 辣么大
    2020-02-05
    写了篇总结,还讨论了单例模式中序列化和反序列化的问题。https://github.com/gdhucoder/DesignPattern/blob/master/021_SingletonPattern.md
    1
    4
  • 小晏子
    2020-02-05
    1.JDK中 java.lang.Runtime是单例实现的,该类用于管理应用程序运行期间的各种信息,比如memory和processor信息,所以从该类的用途可以知道该类必须是单例的。
    2. 使用多个文件,每new一个实例就使用一个新文件,这样就没有文件覆盖问题了。
    1
    4
  • William
    2020-02-18
    为什么说支持懒加载的双重检测不比饿汉式更优?

    因为饿汉式在类加载的时候, 就将实例资源确定好了.
    如果放到懒加载中,实例占用的资源很多,可能导致响应时间过长, 也可能导致(OOM),,根据fail-fast最好事早知道问题,最好提前就暴露出问题.
    1
    3
  • 忆水寒
    2020-02-05
    第一个问题,在我的项目中缓存类的节点设置为单例模式,还有加载全局配置文件的类,也设置为了单例模式。
    第二个问题,我是用消息队列实现的日志收集。
    2
    3
  • 🤤🤤
    2020-02-05
    这篇非常棒,🔨
    
    3
  • L
    2020-08-03
    为什么高版本的jdk单例不再需要volatile修饰,求详细参考资料,感谢������

    new操作 和 初始化操作 设计为原子操作,自然就不存在 指令重排了! 求详细参考资料,感谢������
    
    2