单例模式五种写法

单例模式五种写法

单例模式有五种写法:饿汉、懒汉、双重检验锁、静态内部类、枚举.

单例模式属于设计模式中的创建型模式

一、单例模式应用场景

  • windows的task manager(任务管理器)就是很典型的单例模式;

  • windows的recycle bin(回收站)也是典型的单例应用,在整个系统运行过程中,回收站一直维护仅有的一个实例;

  • 项目中,读取配置文件的类,一般也只有一个对象,没有必要每次使用配置文件数据,每次new一个对象去读取;

  • 应用程序的日志应用,一般都可用单例模式实现,这一般是由于共享的日志文件一直处于打开状态,因为只能有一个实例去操作,否则内容不好追加;

  • 数据库连接池的设计一般也是采用单例模式,因为数据库连接是一种数据库资源;

  • 在spring中,每个bean默认就是单例的,这样做的优点是spring容器可以管理;

  • 在servlet编程中,每个servlet也是单例;

  • 在spring mvc框架,控制器对象也是单例;

单例模式算是设计模式中最容易理解,也是最容易手写代码的模式了。但是其中的坑却不少,所以也常作为面试题来考,本文主要对几种单例写法的整理,并分析其优缺点。

二、合格的单例模式要求

合格的单例模式的实现,至少要保证以下三点:

1. 实现单例功能;
2. 延迟加载;
3. 并发时不出错。 

三、五种单例模式的实现

我们将创建一个 SingleObject 类。SingleObject 类有它的私有构造函数和本身的一个静态实例。

SingleObject 类提供了一个静态方法,供外界获取它的静态实例。SingletonPatternDemo 类使用 SingleObject 类来获取 SingleObject 对象。

image-20240408234756088

1. 饿汉式

这种方式非常简单,因为单例的实例被声明成 static 和 final 变量了,在第一次加载类到内存中时就会初始化,所以创建实例本身是线程安全的

public class Singleton {

    private static final Singleton instance = new Singleton();

    private Singleton() {
    }

    public static Singleton getInstance() {
        return instance;
    }
}
  • 饿汉模式的缺点:可能在还不需要此实例的时候就已经把实例创建出来了,没起到 lazy loading 效果,空间换时间:浪费内存。
  • 饿汉模式的优点:实现简单,线程安全,调用效率高。

2. 懒汉式

public class Singleton {
    
    private static Singleton instance;

    private Singleton() {
    }

    public static Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}
  • 懒汉模式的优点:弥补了饿汉模式的缺点,起到了 lazy loading 的效果,时间换空间:节约内存。
  • 懒汉模式的缺点:多线程并发时有线程安全问题,有可能创建多个实例,也就是说在多线程下不能正常工作。

3. 双重检验锁

双重检验锁模式(double checked locking pattern),是一种使用同步块加锁的方法。

称其为双重检查锁,因为会有两次检查 instance == null,一次是在同步块外,一次是在同步块内。

public class Singleton {
    private static Singleton instance;

    private Singleton() {
    }

    public static Singleton getInstance() {
        if (instance == null) { //第一次检查,提高性能,避免所有的线程都去加锁和释放锁
            synchronized (Singleton.class) {
                if (instance == null) { //第二次检查,确保只有一个线程创建实例
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

为什么在同步块内还要再检验一次?因为可能会有多个线程一起进入同步块外的 if,如果在同步块内不进行二次检验的话就会生成多个实例了

这段代码看起来很完美,很可惜,它是有问题的。**主要在于instance = new Singleton()这句,这并非是一个原子操作,事实上在 JVM 中这句话大概做了下面 3 件事情: **

(1) 给 instance 分配内存

(2) 调用 Singleton 的构造函数来初始化成员变量

(3) 将instance对象指向分配的内存空间(执行完这步 instance 就为非 null 了)

但是在 JVM 的即时编译器(JIT)中存在指令重排序的优化。也就是说上面的第二步和第三步的顺序是不能保证的,最终的执行顺序可能是 1-2-3 也可能是 1-3-2。 如果是后者,则在 3 执行完毕、2 未执行之前,被线程二抢占了,这时 instance 已经是非 null 了(但却没有初始化),所以线程二会直接返回 instance,然后使用,然后顺理成章地报错。

解决办法: 我们只需要将 instance 变量声明成 volatile 修饰就可以了,其作用是防止指令重排序

public class Singleton {
    private volatile static Singleton instance; //声明成 volatile
    private Singleton (){}
 
    public static Singleton getInstance() {
        if (instance == null) {                         
            synchronized (Singleton.class) {
                if (instance == null) {       
                    instance = new Singleton();
                }
            }
        }
        return instance;
    } 
}

使用 volatile 的主要原因是其另一个特性:禁止指令重排序优化。也就是说,在 volatile 变量的赋值操作后面会有一个内存屏障(生成的汇编代码上),读操作不会被重排序到内存屏障之前。比如上面的例子,取操作必须在执行完 1-2-3 之后或者 1-3-2 之后,不存在执行到 1-3 然后取到值的情况。从「先行发生原则」的角度理解的话,就是对于一个 volatile 变量的写操作都先行发生于后面对这个变量的读操作

  • 双校验懒汉模式的优点:弥补了懒汉模式的缺点,防止了并发问题。
  • 双校验懒汉模式的缺点:因为涉及到锁,因此性能有损耗;代码变得更复杂。

4. 静态内部类-登记式

静态内部类(static nested class)

使用静态内部类的方式,也是《Effective Java》上所推荐的。

public class Singleton {  
    private static class SingletonHolder {  
        private static final Singleton INSTANCE = new Singleton();  
    }  
    private Singleton (){}  
    public static final Singleton getInstance() {  
        return SingletonHolder.INSTANCE; 
    }  
}

懒汉式的变种: 静态内部类 空间换时间

首先静态内部类不会随着外部类的加载而加载 ,只有静态内部类的静态成员被调用时才会进行加载也就是初始化 ,这个就是调用静态内部类的静态成员,然后在初始化的过程中new一个对象INSTANCE

这种写法仍然使用JVM本身机制保证了线程安全问题;由于 SingletonHolder 是私有的,除了 getInstance() 之外没有办法访问它,因此它是懒汉式的,同时读取实例的时候不会进行同步,没有性能缺陷;

  • 静态内部类单例的优点:线程安全,支持延迟加载,获取singleton对象时不需要加锁,使用方便。
  • 静态内部类单例的缺点:会增加类的数量,在第一次加载时可能会略微增加启动时间。

5. 枚举

用枚举写单例非常简单,这也是它最大的优点。下面这段代码就是声明枚举实例的通常做法:

public enum EasySingleton{
    INSTANCE;
}

我们可以通过EasySingleton.INSTANCE来访问实例,这比调用getInstance()方法简单多了。创建枚举默认就是线程安全的,所以不需要担心double checked locking,而且还能防止反序列化导致重新创建新的对象。

枚举单例优点:写法简单,实现效率高,因为枚举本身就是单例,由JVM从根本上提供保障,避免通过反射和反序列化的漏洞

枚举单例缺点:没有延迟加载,枚举实例在编译时就已经创建,无法在运行时延迟加载

四、总结

一般来说,单例模式有五种写法:饿汉、懒汉、双重检验锁、静态内部类、枚举

一般情况下,不建议使用第 2 种懒汉方式,建议使用第 1 种饿汉方式。只有在要明确实现 lazy loading 效果时,才会使用第 4 种静态内部类登记方式。如果涉及到反序列化创建对象时,可以尝试使用第 5 种枚举方式。如果有其他特殊的需求,可以考虑使用第 3 种双检锁方式。

  • 单例类的特点:
(1)单例类确保自己只有一个实例

(2)单例类必须自己创建自己的实例

(3)单例类必须为其他对象提供唯一的实例。
  • 单例类的优点:
(1) 控制资源的使用,通过线程同步来控制资源的并发访问。

(2) 控制实例的产生数量,达到节约资源的目的。

(3) 作为通信的媒介,数据共享。他可以在不建立直接关联的条件下,让多个不相关的两个线程或者多个进程之间实现通信。
  • 单例模式的主要缺点如下:
(1) 由于单例模式中没有抽象层,因此单例类的扩展有很大的困难。

(2) 单例类的职责过重,在一定程度上违背了“单一职责原则”。因为单例类既充当了工厂角色,
提供了工厂方法,同时又充当了产品角色,包含一些业务方法,将产品的创建和产品的本身的功能融合到一起。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/549462.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

防范“AI换脸”风险 ZOLOZ Deeper月超2万次攻防测试

4 月 16 日,深度伪造(Deepfake)综合防控产品ZOLOZ Deeper 在北京正式发布,以拦截用户刷脸过程中的“AI换脸”风险,目前已率先应用在身份安全领域。公开资料显示,ZOLOZ是蚂蚁数科的科技品牌,以生…

电商技术揭秘九:搜索引擎中的SEO数据分析与效果评估

相关系列文章 电商技术揭秘一:电商架构设计与核心技术 电商技术揭秘二:电商平台推荐系统的实现与优化 电商技术揭秘三:电商平台的支付与结算系统 电商技术揭秘四:电商平台的物流管理系统 电商技术揭秘五:电商平台的个性…

matplotlib手动调用默认配色

matplotlib 画图有个默认配色方案,在画不同图时会保持一致。如: import numpy as np import matplotlib.pyplot as plt# 图 1 数据 x np.arange(12).astype(np.float32) 1 y1 np.log(x) y2 1 / x y3 np.sin(x) # 图 2 数据 a np.random.randn(200…

关于HTTP1.0、1.1、1.x、2.0、3.0与HTTPS之间的理解

关于HTTP1.0、1.1、1.x、2.0、3.0与HTTPS之间的理解 HTTP的由来 HTTP是Hyper Text Transfer Protocol(超文本传输协议)的缩写。它的发展是万维网协会(World Wide Web Consortium)和Internet工作小组IETF(Internet Eng…

JMeter控制器数据库获取一组数据后遍历输出

目录 1、测试计划中添加Mysql Jar包 2、添加线程组 3、添加 jdbc connection configuration 4、添加JDBC Request,从数据库中获取数据 5.获取数据列表,提取所有goodsName信息 6.通过添加控制器遍历一组数据 6.1 方式一:循环控制器方式 …

Day42:动态规划 LeedCode 01背包 416. 分割等和子集

01背包 1.确定dp数组以及下标的含义 dp[i][j]的含义:从下标为[0-i]的物品里任意取,放进容量为j的背包,价值总和最大是多少。 那么可以有两个方向推出来dp[i][j] 2.确定递推公式 不放物品i:由dp[i - 1][j]推出,即背…

记一次Mysql数据库宕机This could be because you hit a bug.

Hi I’m Shendi 今天收到消息说所有软件不能用了,网页都打不开,遇到了问题,于是在这里记录一下 记一次Mysql数据库宕机This could be because you hit a bug. 起因 为了节省成本,对于小公司而言服务器数量通常不会太多&#xff…

网络安全学习路线-超详细

零基础小白,到就业!入门到入土的网安学习路线! 在各大平台搜的网安学习路线都太粗略了。。。。看不下去了! 建议的学习顺序: 一、网络安全学习普法(心里有个数,要进去坐几年!&#x…

FRDM-MCXN947开发板之RGB灯

一、背景 RGB LED:通过红、绿、蓝三种颜色组合发光的LED,可以理解由三个不同发光属性的LED组成,这个是LCD平板显示原理的基础,一个LED相当于屏幕上面的一个像素 FRDM-MCXN947集成了一块RGB LED,它由三个GPIO口驱动&am…

2024 NTFS读写工具Tuxera NTFS for Mac 是如何进行下载、安装、激活的

本篇将为各位小伙伴们集中讲解一下NTFS读写工具Tuxera NTFS for Mac 是如何进行下载、安装、激活与换机的。 在数字化时代,数据交换和共享变得日益重要。然而,对于Mac用户来说,与Windows系统之间的文件交换可能会遇到一些挑战。这是因为Mac …

【Markdown】调整图片大小和对齐方式

插入图片 使用HTML: <img src"./xxx.png" width "xxx" height "xxx" />比如说我们插入一个图片&#xff0c;系统会自动帮我们生成一个图片链接 把这段链接插入即可 <img src"https://img-blog.csdnimg.cn/direct/66da1f6404…

大模型日报|今日必读的10篇大模型论文

大家好&#xff0c;今日必读的大模型论文来啦&#xff01; 1.谷歌推出新型 Transformer 架构&#xff1a;反馈注意力就是工作记忆 虽然 Transformer 给深度学习带来了革命性的变化&#xff0c;但二次注意复杂性阻碍了其处理无限长输入的能力。 谷歌研究团队提出了一种新型 T…

《自动机理论、语言和计算导论》阅读笔记:p225-p260

《自动机理论、语言和计算导论》学习第 9 天&#xff0c;p225-p260总结&#xff0c;总计 26 页。 一、技术总结 1.pushdown automation(PDA&#xff0c;下推自动机) 2.DPDA Deterministic PDA(确定性下推自动机)。 二、英语总结 1.instantaneous (1)instant: adj. happi…

苹果电脑启动磁盘是什么意思 苹果电脑磁盘清理软件 mac找不到启动磁盘 启动磁盘没有足够的空间来进行分区

当你一早打开苹果电脑&#xff0c;结果系统突然提示&#xff1a; “启动磁盘已满&#xff0c;需要删除部分文件”。你会怎么办&#xff1f;如果你认为单纯靠清理废纸篓或者删除大型文件就能释放你的启动磁盘上的空间&#xff0c;那就大错特错了。其实苹果启动磁盘的清理技巧有很…

app测试系列-超详细的app测试攻略,一文带你学会移动端测试

&#x1f525; 交流讨论&#xff1a;欢迎加入我们一起学习&#xff01; &#x1f525; 资源分享&#xff1a;耗时200小时精选的「软件测试」资料包 &#x1f525; 教程推荐&#xff1a;火遍全网的《软件测试》教程 &#x1f4e2;欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1…

模板初阶的学习

目录&#xff1a; 一&#xff1a;泛型模板 二&#xff1a;函数模板 三&#xff1a;类模板 1&#xff1a;泛型模板 泛型编程&#xff1a;编写与类型无关的通用代码&#xff0c;是代码复用的一种手段。模板是泛型编程的基础。 以交换函数为列进行讲解&#xff1a; void Swap(…

【C++对于C语言的扩充】函数重载、引用以及内联函数

文章目录 &#x1f680;前言&#x1f680;函数重载注意&#xff1a;✈️为什么C可以实现函数重载&#xff0c;而C语言却不行呢&#xff1f; &#x1f680;引用✈️引用的特性✈️C中为什么要引入引用✈️引用与指针的区别 &#x1f680;内联函数✈️内联函数特性 &#x1f680;…

Python 序列化与反序列化

目录 1、基本概念 2、JSON模块 2.1、dumps() 与 loads() 函数 2.2、dump() 与 load() 函数 2.3、bool 、None 类型的序列化与反序列化 3、pickle模块 3.1、dumps() 与 loads() 函数 3.2、dump() 与 load() 函数 1、基本概念 说明&#xff1a;通过文件操作&#xff0c;…

移动硬盘盒支持PD充电:优势解析与实际应用探讨

随着科技的飞速发展&#xff0c;数据存储和传输的需求日益增长&#xff0c;移动硬盘盒作为便携式存储设备的重要载体&#xff0c;其功能和性能也在不断提升。近年来&#xff0c;越来越多的移动硬盘盒开始支持PD&#xff08;Power Delivery&#xff09;充电技术&#xff0c;这一…

案例研究|众乐邦将MeterSphere持续测试平台融入DevOps流水线

众乐邦网络科技有限公司&#xff08;以下简称为“众乐邦”&#xff09;是一家企业服务公司。其旗下的众乐邦灵活用工数字化薪税管理平台&#xff08;以下简称为灵活用工管理平台&#xff09;&#xff0c;以财税服务视角切入灵活用工场景&#xff0c;连接企业、灵活就业者和监管…
最新文章