单例模式有几种写法?请谈谈你的理解?
为什么有单例模式?
单例模式(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/12): K8s 核心概念白话解读(上):Pod 和 Deployment 究竟是什么?
大家好,欢迎来到《云原生核心技术》系列的第七篇! 在上一篇,我们成功地使用 Minikube 或 kind 在自己的电脑上搭建起了一个迷你但功能完备的 Kubernetes 集群。现在,我们就像一个拥有了一块崭新数字土地的农场主,是时…...
零门槛NAS搭建:WinNAS如何让普通电脑秒变私有云?
一、核心优势:专为Windows用户设计的极简NAS WinNAS由深圳耘想存储科技开发,是一款收费低廉但功能全面的Windows NAS工具,主打“无学习成本部署” 。与其他NAS软件相比,其优势在于: 无需硬件改造:将任意W…...
C++:std::is_convertible
C++标志库中提供is_convertible,可以测试一种类型是否可以转换为另一只类型: template <class From, class To> struct is_convertible; 使用举例: #include <iostream> #include <string>using namespace std;struct A { }; struct B : A { };int main…...

【Redis技术进阶之路】「原理分析系列开篇」分析客户端和服务端网络诵信交互实现(服务端执行命令请求的过程 - 初始化服务器)
服务端执行命令请求的过程 【专栏简介】【技术大纲】【专栏目标】【目标人群】1. Redis爱好者与社区成员2. 后端开发和系统架构师3. 计算机专业的本科生及研究生 初始化服务器1. 初始化服务器状态结构初始化RedisServer变量 2. 加载相关系统配置和用户配置参数定制化配置参数案…...
蓝桥杯 2024 15届国赛 A组 儿童节快乐
P10576 [蓝桥杯 2024 国 A] 儿童节快乐 题目描述 五彩斑斓的气球在蓝天下悠然飘荡,轻快的音乐在耳边持续回荡,小朋友们手牵着手一同畅快欢笑。在这样一片安乐祥和的氛围下,六一来了。 今天是六一儿童节,小蓝老师为了让大家在节…...

自然语言处理——循环神经网络
自然语言处理——循环神经网络 循环神经网络应用到基于机器学习的自然语言处理任务序列到类别同步的序列到序列模式异步的序列到序列模式 参数学习和长程依赖问题基于门控的循环神经网络门控循环单元(GRU)长短期记忆神经网络(LSTM)…...

tree 树组件大数据卡顿问题优化
问题背景 项目中有用到树组件用来做文件目录,但是由于这个树组件的节点越来越多,导致页面在滚动这个树组件的时候浏览器就很容易卡死。这种问题基本上都是因为dom节点太多,导致的浏览器卡顿,这里很明显就需要用到虚拟列表的技术&…...

如何理解 IP 数据报中的 TTL?
目录 前言理解 前言 面试灵魂一问:说说对 IP 数据报中 TTL 的理解?我们都知道,IP 数据报由首部和数据两部分组成,首部又分为两部分:固定部分和可变部分,共占 20 字节,而即将讨论的 TTL 就位于首…...
PAN/FPN
import torch import torch.nn as nn import torch.nn.functional as F import mathclass LowResQueryHighResKVAttention(nn.Module):"""方案 1: 低分辨率特征 (Query) 查询高分辨率特征 (Key, Value).输出分辨率与低分辨率输入相同。"""def __…...

2025年渗透测试面试题总结-腾讯[实习]科恩实验室-安全工程师(题目+回答)
安全领域各种资源,学习文档,以及工具分享、前沿信息分享、POC、EXP分享。不定期分享各种好玩的项目及好用的工具,欢迎关注。 目录 腾讯[实习]科恩实验室-安全工程师 一、网络与协议 1. TCP三次握手 2. SYN扫描原理 3. HTTPS证书机制 二…...