[设计模式]单例模式

本章笔记的内容主要参考《设计模式之美》

1.为什么要使用单例? 2.单例存在的问题? 3.单例与静态类的区别? 4.替代方案?

/在很多场景中,我们需要一些可以共享的对象,来统一操作一些资源。若此时,产生了多个实例,则这些原本应该共享的资源,会产生冲突或覆盖的现象。

举个例子,比如日志记录类。一般来说,日志纪录类会像固定的文件中输出日志结果,此时若使用多个实例进行这一操作,对于文件内容的write操作可能会出现覆盖的现象。当然,这种情况下可以使用类级的锁来保证正确性,但相比而言,单例是一种更节约资源的做法。另外,在业务系统中,涉及到如配置、唯一ID生成器这样的需求,一般也会使用单例模式。实际上,在Spring中管理的Bean对象都是基于单例模式的。

1.饿汉模式

public class IdGenerator {
    private AtomicLong id = new AtomicLong(0);
    private static final IdGenerator instance = new IdGenerator();
    private IdGenerator() {}
    public static IdGenerator getInstance() {
        return instance;
    }
    public long getId() {
        return id.incrementAndGet();
    }
}

2.懒汉模式

public class IdGenerator {
    private AtomicLong id = new AtomicLong(0);
    private static IdGenerator instance;
    private IdGenerator() {}
    public static synchronized IdGenerator getInstance() {
        if (instance == null) {
        instance = new IdGenerator();
        }
        return instance;
    }
    public long getId() {
        return id.incrementAndGet();
    }
}

基础版本中使用了对方法加锁的方式,会极大的影响性能,不支持高并发。

public class IdGenerator {
    private AtomicLong id = new AtomicLong(0);
    private static IdGenerator instance;
    private IdGenerator() {}
    public static synchronized IdGenerator getInstance() {
        if (instance == null) {
        instance = new IdGenerator();
        }
        return instance;
    }
    public long getId() {
        return id.incrementAndGet();
    }
}

实际上大多数的实现中,还会给单例类上声明volatile关键字,来避免new动作非原子性导致的问题。具体的可以参考:

实际上这个问题在高版本的JDK中已经做了相应的原子化处理,即使不使用volatile,也能保证正确性。

public class IdGenerator {
    private AtomicLong id = new AtomicLong(0);
    private IdGenerator() {}
    private static class SingletonHolder{
        private static final IdGenerator instance = new IdGenerator();
    }
    public static IdGenerator getInstance() {
        return SingletonHolder.instance;
    }
    public long getId() {
        return id.incrementAndGet();
    }
}

使用了一种简单的方式,达到了懒加载和线程安全的目的。线程安全由JVM在初始化静态内部类时保证。

还可以使用枚举的方式来实现单例,在此不再赘述。

由于单例隐藏了初始化的细节,因此在初始化时,往往使用了硬编码的方式,这其实是一种反模式。在后续维护中,若业务需求发生了变化,相应的逻辑变更会相对困难。这里引用一个《设计模式之美》中的例子:

在系统设计初期,我们觉得系统中只应该有一个数据库连接池,这样能方便我们控制对数据 库连接资源的消耗。所以,我们把数据库连接池类设计成了单例类。但之后我们发现,系统 中有些 SQL 语句运行得非常慢。这些 SQL 语句在执行的时候,长时间占用数据库连接资 源,导致其他 SQL 请求无法响应。为了解决这个问题,我们希望将慢 SQL 与其他 SQL 隔 离开来执行。为了实现这样的目的,我们可以在系统中创建两个数据库连接池,慢 SQL 独 享一个数据库连接池,其他 SQL 独享另外一个数据库连接池,这样就能避免慢 SQL 影响到 其他 SQL 的执行。

另外,单例模式的可测试性不高这也是因为代码中存在较多的硬编码,导致一些输入难以mock测试

在讨论单例模式时,我们会强调其唯一性。但在不同的前提条件下,唯一性的语义是不同的,在默认的语境中,单例模式指的是在同一个进程中,一个类仅有一个对象。当然这个前提条件可能会因为业务落地的实际场景发生变化。

我们如何实现一个类在一个线程中的唯一性?实际上可以直接使用Java中的ThreadLocal类帮助我们实现,或者我们也可以自己在类中定义一个静态的ConcurrentHashMap,并使用线程id为Key,不同的实例为Value进行实现。

那么,在分布式多节点的环境下,如何保证实例的唯一性呢?通常的做法是,使用分布式文件系统,创建一个多节点共享的文件(该实例的本质是唯一的文件)。我们在创建、修改、读取实例时,我们总是从文件中反序列化得到实例,然后进行操作,最后将实例重新序列化回文件。这样就可以在不同的节点上,保证实例的唯一性。

  1. 《设计模式之美》
  2. 《双重检测》
  3. Reality Check, Douglas C. Schmidt, C++ Report, SIGS, Vol. 8, No. 3, March 1996.

Original: https://www.cnblogs.com/xy1997/p/16634509.html
Author: xingyuanyuan
Title: [设计模式]单例模式

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/588073/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • 快速排序和栈溢出

    以前学算法的时候用的《啊哈!算法》,关于算法的各类书籍对于新手都很不友好,这本书绝对是新手第一本书。 当然啦,如果你是大佬天才,直接跳过这本书。 下面介绍《啊哈!算法》中关于快排的…

    数据结构和算法 2023年6月8日
    080
  • Manacher算法 (马拉车算法)

    1 #include 2 #include<string.h> 3 #include 4 using namespace std; 5 6 char s[1000]; …

    数据结构和算法 2023年6月7日
    091
  • python3 Softmax函数

    Softmax函数公式 Softmax的作用简单的说就计算一组数值中每个值的占比 import torch import torch.nn.functional as F 原始数据…

    数据结构和算法 2023年6月16日
    083
  • 深入C++04:模板编程

    📕模板编程 函数模板 模板意义:对类型也进行参数化; 函数模板:是不编译的,因为类型不知道 模板的实例化:函数调用点进行实例化,生成模板函数 模板函数:这才是要被编译器所编译的 函…

    数据结构和算法 2023年6月12日
    088
  • 蓝桥杯 ALGO-986藏匿的刺客(贪心)

    试题 算法训练 藏匿的刺客 资源限制 时间限制:1.0s 内存限制:256.0MB 问题描述 强大的kAc建立了强大的帝国,但人民深受其学霸及23文化的压迫,于是勇敢的鹏决心反抗。…

    数据结构和算法 2023年6月7日
    092
  • 【重要】LeetCode 745. 前缀和后缀搜索

    题目链接 745. 前缀和后缀搜索 注意事项 预先计算出每个单词的前缀后缀组合可能性,用特殊符号连接,作为键,对应的最大下标作为值保存入哈希表。检索时,同样用特殊符号连接前后缀,在…

    数据结构和算法 2023年6月8日
    076
  • 【题解】统计子矩阵

    404. 抱歉,您访问的资源不存在。 可能是网址有误,或者对应的内容被删除,或者处于私有状态。 代码改变世界,联系邮箱 contact@cnblogs.com 园子的商业化努力-困…

    数据结构和算法 2023年6月12日
    0107
  • 如何离线安装posh-git

    不用上github 1、下载post-git离线安装包 2,用Powershell执行install.ps1 3.用管理员权限打开powershell,修改策略: set-exec…

    数据结构和算法 2023年6月16日
    0112
  • C++判断操作系统位数

    本文仅发布于:https://www.cnblogs.com/Icys/p/Is64BitSystem.html //判断当前系统是否为64位 BOOL Is64BitSystem…

    数据结构和算法 2023年6月7日
    098
  • Dijkstra算法求最短路

    例题链接 Dijkstra算法是从一个顶点到其余各顶点的最短路径算法,解决的是有权图中最短路径问题。其主要特点是从起始点开始,采用贪心算法的策略,每次遍历到始点距离最近且未访问过的…

    数据结构和算法 2023年6月8日
    092
  • 树的几种存储方法

    本文参考https://oi-wiki.org/graph/tree-basic/ 理论上说,树作为图的一种,可以由图表示方法完全表示,那为什么要特地给出树的存储方法?因为树具有一…

    数据结构和算法 2023年6月7日
    0145
  • 类暗黑破坏神属性系统思路

    序言 暗黑破坏神,流放之路,火炬之光等经典RPG游戏有令人眼花缭乱的角色属性词缀和相应的机制,搭配修改角色属性的装备,技能,Buff等形成很多有趣的流派。此文提供一种类似游戏的角色…

    数据结构和算法 2023年6月8日
    0113
  • Dijkstra在路由选择中的应用

    Dijkstra在路由选择中的应用 前言背景 OSPF协议(Open Shortest Path First,开放最短路径优先)是在1989年由互联网工程任务组(IETF)正式发布…

    数据结构和算法 2023年6月7日
    092
  • 四探循环依赖 → 当循环依赖遇上 BeanPostProcessor,爱情可能就产生了!

    开心一刻 那天知道她结婚了,我整整一个晚上没睡觉,开了三百公里的车来到她家楼下,缓缓的抽了一支烟…… 天渐渐凉了,响起了鞭炮声,迎亲车队到了,那天披着婚纱的…

    数据结构和算法 2023年6月7日
    096
  • [LeetCode] 5933. k 镜像数字的和

    本文介绍了一种求解LeetCode 5399的一种解法。通过模拟寻找十进制镜像数字,然后判断其对应的k进制表示是否也是镜像,直到得到n个镜像数字。 一、摘要 本文介绍了一种通过模拟…

    数据结构和算法 2023年6月7日
    0100
  • 205. 同构字符串

    给定两个字符串 s 和 t ,判断它们是否是同构的。 如果 s 中的字符可以按某种映射关系替换得到 t ,那么这两个字符串是同构的。 每个出现的字符都应当映射到另一个字符,同时不改…

    数据结构和算法 2023年6月8日
    078
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球