单例模式有几种写法?请谈谈你的理解?
为什么有单例模式?
单例模式(Singleton),也叫单子模式,是一种常用的软件设计模式。在应用这个模式时,单例对象的类必须保证只有一个实例存在。许多时候整个系统只需要拥有一个全局对象,这样有利于我们协调系统整体的行为。
实现原理是什么?
构造方法是private+static方法+if语句判断
注意:不同的实现方式它的实现原理肯定是有所区别的,综合来看!!
实现方式有哪些?
懒汉式、双重锁、饿汉式、静态内部类、枚举
懒汉式
- 好处:启动速度快、节省资源,一直到实例被第一次访问,才需要初始化单例、避免空间浪费;
- 缺点:线程不安全,if语句存在竞态条件
单例类
package com.example;/*** @BelongsProject: BigK* @BelongsPackage: com.example* @Author: dengLiMei* @CreateTime: 2023-06-28 10:04* @Description: 单例模式* @Version: 1.0*/
public class Singleton {//提供一个全局变量让全局访问private static Singleton instance;//私有构造方法,堵死外界利用new创建此类实例的可能private Singleton() {}//获得实例的唯一全局访问点public static Singleton GetInstance() {//当多线程来临的时候判断是否为null,此时instance就是临界资源,会实例化多个if (instance == null) {instance = new Singleton();}return instance;}
}
客户端
//反射破坏封装性
Singleton instance1 = Singleton.GetInstance();// 使用反射获取私有构造函数
Constructor<Singleton> constructor = Singleton.class.getDeclaredConstructor();
constructor.setAccessible(true);// 通过反射创建第二个实例
Singleton instance2 = constructor.newInstance();System.out.println(instance1); // 输出第一个实例的内存地址
System.out.println(instance2); // 输出第二个实例的内存地址
这里我是通过反射的方式去获取对象,然后对获取到的对象进行判断,运行代码之后我们会发现:

两个对象的内存地址并不相同,违背了单一性,那我们如何解决这个问题呢?可能屏幕前有些小伙伴想到了加锁的方式去做,没错,我们用大家比较常见的synchronized实现看看吧。
懒汉式变种-synchronized
- 好处:线程安全
- 缺点:并发性能差,synchronized加锁,不管有没有对象都加锁
单例类
package com.example;/*** @BelongsProject: BigK* @BelongsPackage: com.example* @Author: dengLiMei* @CreateTime: 2023-06-28 10:14* @Description: 懒汉单例:在第一次被引用时,才会将自己实例化* @Version: 1.0*/
public class LazySingleton {private static LazySingleton instance;private LazySingleton() {System.out.println("创建一次");}public static LazySingleton GetInstance() {//方法一:加锁-把判断的这部分逻辑上锁//好处:线程安全//缺点:并发性能差,synchronized加锁,不管有没有对象都加锁//解决方案:双重锁synchronized ("") {if (instance == null) {instance = new LazySingleton();}}return instance;}//方法二:同步代码段public static synchronized LazySingleton getSingleton() {if (instance == null) {instance = new LazySingleton();}return instance;}
}
客户端
//懒汉模式:加锁保证线程安全
Runnable r3 = () -> {DoubleLockSingleton s1 = DoubleLockSingleton.GetInstance();
DoubleLockSingleton s2 = DoubleLockSingleton.GetInstance();if (s1 == s2) {System.out.println("两个对象是相同的实例");
}
};Thread t1 = new Thread(r3);
Thread t2 = new Thread(r3);t1.start();
t2.start();

通过运行结果我们会发现两个线程获取到的对象是同一个,实现了单例。
但是大家可以思考一下这样会不会存在什么问题呢?线程因为每次访问 getInstance() 方法时都需要获取锁,即使实例已经被创建,会在高并发环境下其实是比较影响性能的。并且会导致每次调用 getInstance() 方法都需要获取锁,而不是在需要时才创建实例。那我们可不可以当单例对象没有被创建的时候才去加锁呢?双重锁可以做到
懒汉式变种-双重锁
- 好处:实现线程安全地创建实例,而又不会对性能造成太大影响。
- 缺点:无效等待,同步效率地,锁占用资源(反射会破坏单一性)
单例类
package com.example;/*** @BelongsProject: BigK* @BelongsPackage: com.example* @Description: 懒汉单例——双重锁* @Version: 1.0*/
public class DoubleLockSingleton {//volatile:禁止指令重排序(防止部分初始化)private static volatile DoubleLockSingleton instance;private DoubleLockSingleton() {System.out.println("实例化了一次");}//原理:双重if,延迟实例化,避免每次进行同步的性能开销public static DoubleLockSingleton GetInstance() {//第一层判断:先判断实例是否存在,不存在再加锁处理if (instance == null) {synchronized ("") {//第二层判断if (instance == null) {instance = new DoubleLockSingleton();}}}return instance;}
}
客户端
DoubleLockSingleton instance1 = DoubleLockSingleton.GetInstance();// 使用反射获取私有构造函数
Constructor<DoubleLockSingleton> constructor = DoubleLockSingleton.class.getDeclaredConstructor();
constructor.setAccessible(true);// 通过反射创建第二个实例
DoubleLockSingleton instance2 = constructor.newInstance();System.out.println(instance1); // 输出第一个实例的内存地址
System.out.println(instance2); // 输出第二个实例的内存地址
这里我们依旧使用反射去获取单例对象。我们运行看看效果:

发现构造方法被调用了两地,并且获取到的两个对象的地址也不同,依旧是破坏了单例性。
双重锁实现方式是在第一次创建实例的时候同步,以后就不需要同步了。反射的使用让我们的单例类又不攻自破,没关系,咱们还有其他方式——饿汉式
饿汉式
- 优点:类加载阶段创建,保证了线程安全
- 缺点:可能存在没有被使用的可能,造成资源浪费
单例类
package com.example;/*** 饿汉模式:类加载时初始化单例,以后访问时直接返回即可*/
public class HungrySingleton {//类加载阶段就实例化private static final HungrySingleton singleton = new HungrySingleton();private HungrySingleton() {}public static HungrySingleton getInstance() {return singleton;}
}
客户端
//获取单例对象
HungrySingleton singleton = HungrySingleton.getInstance();// 使用反射获取单例对象
try {Class<?> singletonClass = Class.forName("com.example.HungrySingleton");// 获取私有构造函数Constructor<?> constructor = singletonClass.getDeclaredConstructor();constructor.setAccessible(true);// 通过反射实例化对象HungrySingleton singletonReflection = (HungrySingleton) constructor.newInstance();// 验证是否为同一对象System.out.println(singleton == singletonReflection); // 输出 true
} catch (ClassNotFoundException | NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException e) {e.printStackTrace();
}
使用反射获取单例对象,我们看下输出结果:

在整个应用程序的生命周期中,无论是否会用到该单例实例,都会在类加载时创建实例,可能会导致资源的浪费。饿汉模式无法实现延迟加载,即在需要时才创建实例。这可能会导致在应用程序启动时就创建了大量的实例,占用内存。
基于这些原因,尽管饿汉模式是一种简单且线程安全的单例模式实现方式,但在资源利用、延迟加载和异常处理等方面存在一些问题。所以我们在实际使用过程中需要根据具体场景选择合适的单例模式实现方式
静态内部类
好处:
- 懒加载:静态内部类的方式能够实现懒加载,即在需要时才会加载内部类,从而创建单例对象。这样可以避免在类加载时就创建单例对象,节省了资源。
- 线程安全:静态内部类的方式利用了类加载机制和静态变量的特性,能够保证在多线程环境下也能够保持单例的唯一性,而且不需要使用同步关键字。
- 延迟加载:由于静态内部类的加载是在需要时才进行的,因此能够实现延迟加载,即在第一次使用时才会创建单例对象。
缺点:静态内部类的方式需要额外的类加载和内存开销,因为它需要创建一个内部类对象,而内部类对象的创建需要额外的内存开销。
单例类
package com.example;/*** 静态内部类* */
public class StaticInnerSingleton {//静态内部类private static class SingletonHolder {private static final StaticInnerSingleton INSTANCE = new StaticInnerSingleton();}private StaticInnerSingleton (){}public static final StaticInnerSingleton getInstance() {return SingletonHolder.INSTANCE;}}
客户端
//获取单例对象
StaticInnerSingleton singleton = StaticInnerSingleton.getInstance();// 使用反射获取单例对象
try {Class<?> singletonClass = Class.forName("com.example.StaticInnerSingleton");// 获取私有构造函数Constructor<?> constructor = singletonClass.getDeclaredConstructor();constructor.setAccessible(true);// 通过反射实例化对象StaticInnerSingleton singletonReflection = (StaticInnerSingleton) constructor.newInstance();// 验证是否为同一对象System.out.println(singleton == singletonReflection); // 输出 true
} catch (ClassNotFoundException | NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException e) {e.printStackTrace();
}
我们来看看运行结果:

获取的两个对象的地址是不相同的,实现了单例。
它利用了类加载的特性和静态内部类的懒加载特性,解决了饿汉模式的资源浪费和懒汉模式的线程安全问题。具体实现方式是在外部类中定义一个私有的静态内部类,内部类中创建单例实例,并且利用类加载的特性保证了实例的唯一性。同时,由于静态内部类是在需要的时候才加载,因此实现了延迟加载的效果。也是比较推荐的一种方式
枚举
优点:线程安全、防止反序列化重新创建新的对象
单例类
package com.example;/*** 枚举方式*/
public enum EnumSingleton {INSTANCE;
}
客户端
// 获取单例对象
EnumSingleton singleton1 = EnumSingleton.INSTANCE;
EnumSingleton singleton2 = EnumSingleton.INSTANCE;// 验证是否为同一对象
System.out.println(singleton1 == singleton2); // 输出 true
我们来让控制台打印输出看看结果:

在Java中,枚举类型是线程安全的,并且保证在任何情况下都是单例的。因此,使用枚举实现单例模式是一种推荐的方式。具体实现方式是定义一个包含单个枚举常量的枚举类型,这个枚举常量就是单例实例。由于枚举类型在Java中是天然的单例,因此不需要担心线程安全和反射攻击等问题。
使用场景有哪些?
Windows的Task Manager(任务管理器)、回收站
使用时如何选择?

在实际业务场景中,可以根据具体需求选择适合的单例模式。如果需要在应用启动时创建对象,且对性能要求较高,可以选择饿汉式或双重校验锁;如果需要延迟加载对象,可以选择静态内部类或枚举单例模式;如果对线程安全要求较高,可以选择双重校验锁或静态内部类单例模式
如果有想要交流的内容欢迎在评论区进行留言,如果这篇文档受到了您的喜欢那就留下你点赞+收藏+评论脚印支持一下博主~
相关文章:
单例模式有几种写法?请谈谈你的理解?
为什么有单例模式? 单例模式(Singleton),也叫单子模式,是一种常用的软件设计模式。在应用这个模式时,单例对象的类必须保证只有一个实例存在。许多时候整个系统只需要拥有一个全局对象,这样有利…...
帕鲁幻兽 一键开服 简单到爆 教你10秒实现 帕鲁幻兽私服联机服务器搭建
幻兽帕鲁是一款非常受欢迎的游戏,最近在社区中呈现了爆火的趋势,在线人数已经突破了百万级别。由于社区的热度不断上升,官方服务器开始出现了不稳定和卡人闪退的情况。搭建一个私人服务器可能是一个最稳定而舒适的解决方案。通过搭建私人服务…...
自动化报告pptx-python|如何将pandas的表格写入PPTX(二)
本篇延续:自动化报告的前奏|使用python-pptx操作PPT(一) 因为在pptx-python中使用table,需要单个cell逐一输入,于是在想有没有pandas可以直接读入的方式, 有两个开源项目有类似的功能: PandasToPowerpointmspandas其中mspandas写的比较复杂,PandasToPowerpoint比较易懂…...
Ruby详解及安装流程
文章目录 一、Ruby详解二、Ruby安装流程三、Ruby案例四、Ruby常见问题五、Ruby优缺点六、热门文章 一、Ruby详解 Ruby是一种高级编程语言,具有简单易学、灵活多变、优雅美丽的语法特点。它是一种面向对象的编程语言,具有动态类型和解释型语言的特性。在…...
免费的ChatGPT网站 ( 7个 )
ChatGPT的核心功能是基于用户在输入时的语言或文本生成相应的回复或继续内容。此外,它还能够完成多种任务,如撰写邮件、视频脚本、文案、翻译、代码编写以及撰写论文等。 博主归纳总结了7个国内非常好用,而且免费的chatGPT网站,AI…...
python异步编程(1)——理论篇
1.理解多线程 当启动一个Python程序时,它会作为一个单独的进程运行在操作系统中。进程是操作系统分配资源(如内存和处理器时间)的基本单位。每个Python程序启动时,都会创建一个主线程。如果没有在代码中明确创建其他线程…...
PyTorch复现网络模型VGG
VGG 原论文地址:https://arxiv.org/abs/1409.1556VGG是Visual Geometry Group(视觉几何组)的缩写,它是一个在计算机视觉领域中非常有影响力的研究团队,主要隶属于牛津大学的工程系和科学系。VGG以其对卷积神经网络&am…...
Springboot集成Javamelody
JavaMelody的目标是监视QA和生产环境中的Java或Java EE应用服务器。它不是模拟用户请求的工具,而是根据用户对应用程序的使用情况来衡量和计算应用程序实际操作的统计信息的工具。JavaMelody主要基于请求统计和演化图。 它允许改进QA和生产中的应用程序,…...
如何将 h5 页面快速转换成微信小程序
Hello各位朋友们大家新的一月好呀!我是咕噜铁蛋!我知道在小程序开发中,有时候需要将H5页面转换成微信小程序页面。这样可以将原本的网页内容适配到小程序中,让用户能够更方便地访问和使用。在本文中,我将分享如何快速将…...
在Vue的模块开发中使用GPT的体验及总结
我这一周都在忙着实现一个页面,这个页面是通过vue基于element-ui来实现的。在这个过程中,我把页面拆分成多个组件,而组件的生成是通过Chat-GPT3来实现的。 这又是一次使用AI来协同开发的体验,觉得有必要总结一下: 遵循…...
Java常见算法题解析面试题(中)
11.判断101-200之间有多少个素数,并输出所有素数。【重点】 程序分析:判断素数的方法,用一个数分别去除2到sqrt(这个数),如果能被整除,则表明此数不是素数,反之是素数。 public class lianxi { publi…...
提升网站性能的秘诀:为什么Nginx是高效服务器的代名词?
在这个信息爆炸的时代,每当你在浏览器中输入一个网址,背后都有一个强大的服务器在默默地工作。而在这些服务器中,有一个名字你可能听说过无数次——Nginx。今天,就让我们一起探索这个神奇的工具。 一、Nginx是什么 Nginx&#x…...
[Python图像处理] 使用OpenCV创建深度图
使用OpenCV创建深度图 双目视觉创建深度图相关链接双目视觉 在传统的立体视觉中,两个摄像机彼此水平移动,用于获得场景上的两个不同视图(作为立体图像),就像人类的双目视觉系统: 通过比较这两个图像,可以以视差的形式获得相对深度信息,该视差编码对应图像点的水平坐标的…...
vue+element 换肤功能
1.首先建深色和浅色两个主题样式变量样式表,样式表名和按钮中传入的值一样,本例中起名为default.scss和dark.scss 2.在data中定义主题变量名 zTheme:‘defalut’,默认引用defalut.scss, 在点击按钮时切换引用的样式表,达到换肤效果…...
python魔法函数[全面]
1、init 用于初始化对象的属性和状态 当创建一个对象时,Python会自动调用该对象的__init__方法。 这个方法用于初始化对象的属性和状态,是对象创建过程中的一个重要环节 2、new # 通常我们不需要重写__new__方法,除非我们正在进行一些非常…...
python实现贪吃蛇小游戏(附源码)
文章目录 导入所需的模块坐标主游戏循环模块得分 贪吃蛇小游戏,那个曾经陪伴着00后和90后度过无数欢笑时光的熟悉身影,仿佛是一把打开时光之门的钥匙。它不仅是游戏世界的经典之一,更是我们童年岁月中不可或缺的一部分,一个承载回…...
爬虫学习笔记-Cookie登录古诗文网
1.导包请求 import requests 2.获取古诗文网登录接口 url https://so.gushiwen.cn/user/login.aspxfromhttp%3a%2f%2fso.gushiwen.cn%2fuser%2fcollect.aspx # 请求头 headers {User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like …...
Linux网络状态查看与防火墙管理
网络状态查看 netstat [选项] Netstat是一款命令行工具,用于显示Linux系统中网络的状态信息,可以显示网络连接、路由表、连接的数据统计等信息。 使用 选项 -a:显示所有选项,包括监听和未监听的端口。 -t:仅显示tc…...
VxTerm:C++ MFC,在工具栏中增加Edit/ComboBox等组件,打造一个地址栏/搜索栏功能
VxTerm软件可以在本站链接下载:唯一国产化SSH工具下载,单文件纯绿色不需要安装,替代SecureCRT 在软件的主界面中,增加了一个地址栏功能。 本人的文章内容都是经本人亲自实现并验证成功的干货,关注我,互相交…...
【Android】屏幕锁
屏幕锁,就是锁住屏幕不让用户误触摸,从开发者的角度看就是不响应用户的点击事件。 屏幕锁界面 可以自己创建一个布局文件,或者直接创建一个View(例如ImageView)。 参数LayoutParams mLayoutParams new LayoutParam…...
7.4.分块查找
一.分块查找的算法思想: 1.实例: 以上述图片的顺序表为例, 该顺序表的数据元素从整体来看是乱序的,但如果把这些数据元素分成一块一块的小区间, 第一个区间[0,1]索引上的数据元素都是小于等于10的, 第二…...
YSYX学习记录(八)
C语言,练习0: 先创建一个文件夹,我用的是物理机: 安装build-essential 练习1: 我注释掉了 #include <stdio.h> 出现下面错误 在你的文本编辑器中打开ex1文件,随机修改或删除一部分,之后…...
Opencv中的addweighted函数
一.addweighted函数作用 addweighted()是OpenCV库中用于图像处理的函数,主要功能是将两个输入图像(尺寸和类型相同)按照指定的权重进行加权叠加(图像融合),并添加一个标量值&#x…...
macOS多出来了:Google云端硬盘、YouTube、表格、幻灯片、Gmail、Google文档等应用
文章目录 问题现象问题原因解决办法 问题现象 macOS启动台(Launchpad)多出来了:Google云端硬盘、YouTube、表格、幻灯片、Gmail、Google文档等应用。 问题原因 很明显,都是Google家的办公全家桶。这些应用并不是通过独立安装的…...
ServerTrust 并非唯一
NSURLAuthenticationMethodServerTrust 只是 authenticationMethod 的冰山一角 要理解 NSURLAuthenticationMethodServerTrust, 首先要明白它只是 authenticationMethod 的选项之一, 并非唯一 1 先厘清概念 点说明authenticationMethodURLAuthenticationChallenge.protectionS…...
pikachu靶场通关笔记22-1 SQL注入05-1-insert注入(报错法)
目录 一、SQL注入 二、insert注入 三、报错型注入 四、updatexml函数 五、源码审计 六、insert渗透实战 1、渗透准备 2、获取数据库名database 3、获取表名table 4、获取列名column 5、获取字段 本系列为通过《pikachu靶场通关笔记》的SQL注入关卡(共10关࿰…...
通过MicroSip配置自己的freeswitch服务器进行调试记录
之前用docker安装的freeswitch的,启动是正常的, 但用下面的Microsip连接不上 主要原因有可能一下几个 1、通过下面命令可以看 [rootlocalhost default]# docker exec -it freeswitch fs_cli -x "sofia status profile internal"Name …...
Neko虚拟浏览器远程协作方案:Docker+内网穿透技术部署实践
前言:本文将向开发者介绍一款创新性协作工具——Neko虚拟浏览器。在数字化协作场景中,跨地域的团队常需面对实时共享屏幕、协同编辑文档等需求。通过本指南,你将掌握在Ubuntu系统中使用容器化技术部署该工具的具体方案,并结合内网…...
WebRTC调研
WebRTC是什么,为什么,如何使用 WebRTC有什么优势 WebRTC Architecture Amazon KVS WebRTC 其它厂商WebRTC 海康门禁WebRTC 海康门禁其他界面整理 威视通WebRTC 局域网 Google浏览器 Microsoft Edge 公网 RTSP RTMP NVR ONVIF SIP SRT WebRTC协…...
Windows 下端口占用排查与释放全攻略
Windows 下端口占用排查与释放全攻略 在开发和运维过程中,经常会遇到端口被占用的问题(如 8080、3306 等常用端口)。本文将详细介绍如何通过命令行和图形化界面快速定位并释放被占用的端口,帮助你高效解决此类问题。 一、准…...
