单例模式有几种写法?请谈谈你的理解?
为什么有单例模式?
单例模式(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…...
KubeSphere 容器平台高可用:环境搭建与可视化操作指南
Linux_k8s篇 欢迎来到Linux的世界,看笔记好好学多敲多打,每个人都是大神! 题目:KubeSphere 容器平台高可用:环境搭建与可视化操作指南 版本号: 1.0,0 作者: 老王要学习 日期: 2025.06.05 适用环境: Ubuntu22 文档说…...
深入剖析AI大模型:大模型时代的 Prompt 工程全解析
今天聊的内容,我认为是AI开发里面非常重要的内容。它在AI开发里无处不在,当你对 AI 助手说 "用李白的风格写一首关于人工智能的诗",或者让翻译模型 "将这段合同翻译成商务日语" 时,输入的这句话就是 Prompt。…...
Spark 之 入门讲解详细版(1)
1、简介 1.1 Spark简介 Spark是加州大学伯克利分校AMP实验室(Algorithms, Machines, and People Lab)开发通用内存并行计算框架。Spark在2013年6月进入Apache成为孵化项目,8个月后成为Apache顶级项目,速度之快足见过人之处&…...
Xshell远程连接Kali(默认 | 私钥)Note版
前言:xshell远程连接,私钥连接和常规默认连接 任务一 开启ssh服务 service ssh status //查看ssh服务状态 service ssh start //开启ssh服务 update-rc.d ssh enable //开启自启动ssh服务 任务二 修改配置文件 vi /etc/ssh/ssh_config //第一…...
Cilium动手实验室: 精通之旅---20.Isovalent Enterprise for Cilium: Zero Trust Visibility
Cilium动手实验室: 精通之旅---20.Isovalent Enterprise for Cilium: Zero Trust Visibility 1. 实验室环境1.1 实验室环境1.2 小测试 2. The Endor System2.1 部署应用2.2 检查现有策略 3. Cilium 策略实体3.1 创建 allow-all 网络策略3.2 在 Hubble CLI 中验证网络策略源3.3 …...
Java多线程实现之Callable接口深度解析
Java多线程实现之Callable接口深度解析 一、Callable接口概述1.1 接口定义1.2 与Runnable接口的对比1.3 Future接口与FutureTask类 二、Callable接口的基本使用方法2.1 传统方式实现Callable接口2.2 使用Lambda表达式简化Callable实现2.3 使用FutureTask类执行Callable任务 三、…...
linux 下常用变更-8
1、删除普通用户 查询用户初始UID和GIDls -l /home/ ###家目录中查看UID cat /etc/group ###此文件查看GID删除用户1.编辑文件 /etc/passwd 找到对应的行,YW343:x:0:0::/home/YW343:/bin/bash 2.将标红的位置修改为用户对应初始UID和GID: YW3…...
使用 Streamlit 构建支持主流大模型与 Ollama 的轻量级统一平台
🎯 使用 Streamlit 构建支持主流大模型与 Ollama 的轻量级统一平台 📌 项目背景 随着大语言模型(LLM)的广泛应用,开发者常面临多个挑战: 各大模型(OpenAI、Claude、Gemini、Ollama)接口风格不统一;缺乏一个统一平台进行模型调用与测试;本地模型 Ollama 的集成与前…...
服务器--宝塔命令
一、宝塔面板安装命令 ⚠️ 必须使用 root 用户 或 sudo 权限执行! sudo su - 1. CentOS 系统: yum install -y wget && wget -O install.sh http://download.bt.cn/install/install_6.0.sh && sh install.sh2. Ubuntu / Debian 系统…...
C++:多态机制详解
目录 一. 多态的概念 1.静态多态(编译时多态) 二.动态多态的定义及实现 1.多态的构成条件 2.虚函数 3.虚函数的重写/覆盖 4.虚函数重写的一些其他问题 1).协变 2).析构函数的重写 5.override 和 final关键字 1&#…...
