Java设计模式——单例模式(特性、各种实现、懒汉式、饿汉式、内部类实现、枚举方式、双重校验+锁)

文章目录

    • 单例模式1️⃣
      • 特性💪
      • 单例模式的类型与实现:
        • 类型
        • 懒汉式实现(线程不安全)
        • 懒汉式实现(线程安全)
        • 双重锁校验懒汉式(线程安全)
        • 饿汉式实现(线程安全)
        • 使用类的内部类实现⭐
        • 枚举方式实现单例(推荐)👍

单例模式1️⃣

单例模式是指在内存中只会创建且仅创建一次对象的设计模式。让你能够保证一个类只有一个实例, 并提供一个访问该实例的全局节点。

特性💪

  1. 唯一性:整个系统中,单例类只能有一个实例。
  2. 私有化构造函数:防止外部通过new关键字直接创建对象。
  3. 静态方法提供全局访问点:通常使用getInstance()方法来获取该类的唯一实例。
  4. 延迟实例化(懒加载):有些实现会在第一次调用getInstance()时才创建实例,以节省资源。

单例模式的类型与实现:

类型
  1. 懒汉式:在真正需要使用对象时才去创建该单例类对象
  2. 饿汉式:在类加载时已经创建好该单例对象,等待被程序使用
懒汉式实现(线程不安全)

只有当调用getInstance()方法时才会创建单例对象,这种方式实现了延迟加载,但是需要考虑多线程环境下的线程安全问题。

img

public class Singleton {private static Singleton instance;private Singleton() {}public static Singleton getInstance() {if (instance == null) {instance = new Singleton();}return instance;}
}
  • 单例模式不允许外部直接创建,所以构造函数添加私有属性private
  • 这种方式满足懒汉式,但是在并发场景下,多个线程使用单例对象可能导致实例并存,从而违反了单例要求
懒汉式实现(线程安全)

上述懒汉式实现是线程不安全的(例如同时两个线程去获取单例对象,如果此时单例对象还未创建,可能会导致同事创建两个对象,从而违反单例),故我们要解决线程安全问题。

img

最容易想到的方法:使用锁(synchronized)给类加锁来保证线程安全

public class Singleton {private static Singleton instance;private Singleton() {}public static synchronized Singleton getInstance() {if (instance == null) {instance = new Singleton();}return instance;}
}
双重锁校验懒汉式(线程安全)

但是上述方法每次获取对象的时候都要去先获取锁,并发性能不是很好

可以进行优化:(如果没有实例化则加锁创建,如果实例化了则直接获取,可以使得已经实例化的单例对象在获取单例对象时无需先获取锁,而是直接获取对象)

使用Double Check(双重校验) + Lock(加锁) 的写法:

public class Singleton {private static volatile Singleton instance;private Singleton() {}public static Singleton getInstance() {if (instance == null) {  // 第一次检查,为了避免不必要的同步操作,提高性能。synchronized(Singleton.class) { if (instance == null) {  // 第二次检查,以确保即使在多线程环境下也只创建一个实例。instance = new Singleton();}}}return instance;}
}

同时我们也使用了volatile关键字去确保instance变量的更新对所有线程都是立即可见的,并且禁止指令重排序,保证多线程环境下的正确性

  1. 防止指令重排序: 在多线程环境下,JVM为了优化性能可能会对指令进行重排序。对于单例对象的创建而言,构造函数内部的操作可能被重排序到对象引用赋值之后。例如,如果一个线程正在执行实例化操作,它可能会先将对象引用设置为非空(即指向一块内存),然后再完成对象的初始化。这种情况下,另一个线程可能看到的是一个部分初始化的对象(因为对象的引用不是null了),这会导致不可预测的行为或错误。volatile关键字可以禁止这种指令重排序,保证对象完全初始化之后才会被其他线程看到。
  2. 可见性保证volatile关键字确保了一个线程对共享变量(在这个场景下是单例对象的引用)的修改对于其他线程是立即可见的。也就是说,当一个线程成功创建了单例对象后,所有其他线程都能看到这个对象已经被正确地初始化了,而不会读取到旧的或者默认的值(如null)。这避免了多个线程同时创建多个实例的情况。
饿汉式实现(线程安全)

在类加载的时候就创建好单例对象,这种方式简单但不够灵活,因为它不能做到延迟加载。

public class Singleton{private static final Singleton singleton = new Singleton();private Singleton(){}public static Singleton getInstance() {return singleton;}
}

在类加载的时候,private static final Singleton singleton = new Singleton();这行代码已经实例化好了一个单例对象在内存中。

使用类的内部类实现⭐

利用了Java语言的类加载机制,只有当调用getInstance()方法时,内部类才会被加载,从而实现了懒加载和线程安全,同时不会因为加锁的方式耗费性能。

public class Singleton {private Singleton() {}private static class SingletonHolder {private static final Singleton INSTANCE = new Singleton();}public static Singleton getInstance() {return SingletonHolder.INSTANCE;}
}
  • 这主要是因为JVM虚拟机可以保证多线程并发访问的正确性,也就是一个类的构造方法在多线程环境下可以被正确的加载。
  • 此种方式也是非常推荐使用的一种单例模式
枚举方式实现单例(推荐)👍

在Java中,使用枚举(Enum)来实现单例模式是一种非常简洁且高效的方法。枚举类型的单例不仅能够防止反射攻击和序列化导致的重复实例化问题,而且代码量极少,易于理解和维护。这是因为Java的枚举机制保证了每个枚举常量的唯一性,并且在类加载时自动初始化

public enum Singleton {INSTANCE;private final String property;// 初始化属性值Singleton() {this.property = "Some Value";}public String getProperty() {return property;}// 其他业务方法public void doSomething() {// 方法逻辑...}
}public class Client {public static void main(String[] args) {Singleton singleton = Singleton.INSTANCE;singleton.doSomething();System.out.println(singleton.getProperty());}
}

枚举单例的优点:

  1. 天然线程安全:由于枚举常量在类加载时就被初始化,所以不需要额外的同步代码来保证线程安全。
  2. 防止反序列化创建新实例:枚举类型具有内在的序列化机制,如果尝试反序列化一个枚举类型的对象,它总是返回现有的枚举常量,而不会创建新的实例。
  3. 防止反射攻击:即使通过反射调用私有构造函数,也无法创建新的枚举实例。
  4. 简洁:相比于其他单例模式的实现方式,枚举单例的代码更加简洁明了。
  5. 延迟加载:虽然枚举类型不是天生支持懒加载,但是可以通过将实际的工作委托给另一个静态内部类来实现这一点。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

STM32 FreeRTOS中断管理

目录 FreeRTOS的中断管理 1、STM32中断优先级管理 2、FreeRTOS任务优先级管理 3、寄存器和内存映射寄存器 4、BASEPRI寄存器 5、FreeRTOS与STM32中断管理结合使用 vPortRaiseBASEPRI vPortSetBASEPRI 6、FromISR后缀 7、在中断服务函数中调用FreeRTOS的API函数需注意 F…

[Spring] SpringCloud概述与环境工程搭建

🌸个人主页:https://blog.csdn.net/2301_80050796?spm1000.2115.3001.5343 🏵️热门专栏: 🧊 Java基本语法(97平均质量分)https://blog.csdn.net/2301_80050796/category_12615970.html?spm1001.2014.3001.5482 🍕 Collection与…

【大厂面试AI算法题中的知识点】方向涉及:ML/DL/CV/NLP/大数据...本篇介绍自动驾驶检测模型如何针对corner case 优化?

【大厂面试AI算法题中的知识点】方向涉及:ML/DL/CV/NLP/大数据…本篇介绍自动驾驶检测模型如何针对corner case 优化? 【大厂面试AI算法题中的知识点】方向涉及:ML/DL/CV/NLP/大数据…本篇介绍自动驾驶检测模型如何针对corner case 优化&…

Swift语言的软件工程

Swift语言的软件工程 引言 随着科技的不断进步,软件开发行业正在经历着前所未有的变化。在这场变革中,Swift语言作为苹果公司推出的一种新型编程语言,凭借其简洁、高效及安全的特性,正在快速崛起,成为现代软件工程中…

mobaxterm内置编辑器中文出现乱码如何解决:直接更换编辑器为本地编辑器

诸神缄默不语-个人CSDN博文目录 使用场景是我需要用mobaxterm通过SSH的方式登录服务器,进入服务器之后我就直接打开代码文件,mobaxterm会直接用内置的编辑器(MobaTextEditor)打开,但这会导致中文编程乱码。 我一开始是…

机器学习与人工智能的关系

机器学习与人工智能的关系 一、人工智能二、机器学习2.1 机器学习与人工智能的关系2.2 机器学习的本质 三、其他玩艺 曾几何时,人工智能还是个科幻名词,仿佛只属于未来世界。如今,它已经渗透到了我们生活的方方面面,成为顶流。我们…

一些常见的Java面试题及其答案

Java基础 1. Java中的基本数据类型有哪些? 答案:Java中的基本数据类型包括整数类型(byte、short、int、long)、浮点类型(float、double)、字符类型(char)和布尔类型(boo…

前端js 复制文本到剪切板

在JavaScript中,你可以使用navigator.clipboard.writeText()方法将文字复制到剪切板。这个方法返回一个Promise,当复制操作完成时会resolve。 function copyTextToClipboard(text) {// 检查浏览器是否支持 Clipboard APIif (navigator.clipboard &&…

构建高性能网络服务:从 Socket 原理到 Netty 应用实践

1. 引言 在 Java 网络编程中,Socket 是实现网络通信的基础(可以查看我的上一篇博客)。它封装了 TCP/IP 协议栈,提供了底层通信的核心能力。而 Netty 是在 Socket 和 NIO 的基础上,进一步封装的高性能、异步事件驱动的…

Docker PG流复制搭建实操

目录标题 制作镜像1. 删除旧的容器2. 创建并配置容器3. 初始化数据库并启动 主库配置参数4. 配置主库5. 修改 postgresql.conf 配置 备库配置参数6. 创建并配置备库容器7. 初始化备库 流复制8. 检查主库复制状态9. 检查备库配置 优化建议问题1:FATAL: using recover…

Elasticsearch 批量导入数据(_bluk方法)

官方API&#xff1a;https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-bulk.html 建议先看API POST /<索引名>/_bulk 格式要求&#xff1a; POST _bulk { "index" : { "_index" : "test", "_id" : &q…

思维转换:突破思维桎梏,创造更高效的工作与生活

在现代职场和生活中&#xff0c;我们经常面临着各种挑战和问题&#xff0c;有时候虽然付出了很多努力&#xff0c;但依然难以找到更有效的解决方案。这时&#xff0c;或许我们需要的不是更多的努力&#xff0c;而是一次“思维转换”。这一概念看似简单&#xff0c;但它背后却蕴…

后端代码方式导出excle模板

目录 1.接口层2.业务层3.前端请求4.或者直接用tomcat方式导出 1.接口层 PostMapping("/exprotExcelTemplate")public void exprotExcelTemplate(HttpServletResponse response) throws Exception {exportExcelService.exprotExcelTemplate(response);}2.业务层 Overr…

Active Prompting with Chain-of-Thought for Large Language Models

题目 大型语言模型的思维链主动提示 论文地址&#xff1a;https://arxiv.org/abs/2302.12246 项目地址&#xff1a;https://github.com/shizhediao/active-prompt 摘要 大型语言模型(LLM)规模的不断扩大为各种需要推理的复杂任务带来了涌现能力&#xff0c;例如算术和常识推理…

Windows图形界面(GUI)-QT-C/C++ - QT控件创建管理初始化

公开视频 -> 链接点击跳转公开课程博客首页 -> ​​​链接点击跳转博客主页 目录 控件创建 包含对应控件类型头文件 实例化控件类对象 控件设置 设置父控件 设置窗口标题 设置控件大小 设置控件坐标 设置文本颜色和背景颜色 控件排版 垂直布局 QVBoxLayout …

04、Redis深入数据结构

一、简单动态字符串SDS 无论是Redis中的key还是value&#xff0c;其基础数据类型都是字符串。如&#xff0c;Hash型value的field与value的类型&#xff0c;List型&#xff0c;Set型&#xff0c;ZSet型value的元素的类型等都是字符串。redis没有使用传统C中的字符串而是自定义了…

Require:利用MySQL binlog实现闪回操作

1&#xff0c;闪回原理 【binlog】MySQL binlog以event的形式&#xff0c;记录了MySQL server从启用binlog以来所有的变更信息&#xff0c;能够帮助重现这之间的所有变化。MySQL引入binlog主要有两个目的&#xff1a;一是为了主从复制&#xff1b;二是某些备份还原操作后需要重…

traceroute原理探究

文章中有截图&#xff0c;看不清的话&#xff0c;可以把浏览器显示比例放大到200%后观看。 linux下traceroute的原理 本文通过抓包观察一下linux下traceroute的原理 环境&#xff1a;一台嵌入式linux设备&#xff0c;内网ip是192.168.186.195&#xff0c;其上有192.168.202.…

DM表结构修改

DM 表结构修改 注&#xff1a;达梦数据库在操作数据库的时候需要带上模式名、即 模式名表名 模式名 – 也就是数据库名 索引 一个 [模式] 下&#xff0c;[索引名称] 唯一 创建索引 示例&#xff1a; # 语法 CREATE INDEX "[索引名称]" ON "[模式名]".&q…

无源器件-电容

电容器件的参数 基本概念由中学大学物理或电路分析内容获得&#xff0c;此处不做过多分析。 电容的产量占全球电子元器件产品的40%以上。 单位&#xff1a;法拉 F&#xff1b;1F10^6uF&#xff1b;电路中常见的104电容就是10*10^4pF100nF0.1uF C为电容&#xff0c;Rp为绝缘电…