【设计模式】单例模式详解

单例模式:

  • 定义:确保一个类在任何情况下都绝对只有一个实例,并提供一个全局访问点。为了防止出现重复的创建。
    单例模式是JAVA中最简单的设计模式之一。属于创建型设计模式,它提供了一种创建对象的最佳方式。
    这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有一个对象被创建。这个类提供了访问唯一对象的方法,可以直接访问,不需要new。
饿汉式:程序起始就初始化对象。
public class HungrySingle {// 私有空参构造方法,让初始化无法轻易被别人调用 (反射可以进行调用)private HungrySingle() {}// 在类中创建该类对象private static final HungrySingle single = new  HungrySingle();// 提供公共的访问方式,提供外接进行访问public static HungrySingle getInstance() {return single;}
}
  • 优点:
  1. 线程安全:由于实例在类加载时就创建,所以不存在多线程环境下的线程安全问题。
  2. 简单易用:实现简单,不需要考虑多线程同步等复杂性。
  • 缺点:
  1. 浪费资源:在程序启动时即创建实例,如果该实例在后续程序运行中很少被使用,就会造成资源浪费。
  2. 灵活性受限:无法实现延迟加载,不适合在实例创建过程中需要执行复杂操作的情况。
  3. 可能造成加载缓慢:如果实例初始化较为耗时,会导致程序启动较慢。
懒汉式:被外部类调用才创建实例
public class LazySingle {// 声明私有构造方法private LazySingle(){};// 单例对象实例private static LazySingle single;// 获取单例对象的静态方法public static synchronized LazySingle getInstance() {// 如果实例为空,则创建新实例if (single == null) {single = new LazySingle();}return single;}
}
  • 优点:
  1. 延迟加载:只有在需要时才创建实例,避免了资源的浪费。
  2. 灵活性高:可以根据需要进行实例化,适用于实例初始化较为复杂或耗时的情况。
  • 缺点:
  1. 性能受影响:由于需要在获取实例时进行同步操作,可能会影响性能。
  2. 复杂度增加:需要考虑线程安全性,可能需要使用同步锁等机制,增加了代码复杂度。
  3. 可能存在线程安全问题:在某些情况下,同步机制可能会引入死锁等问题,需要谨慎设计。
DCL双重检测锁
public class DoubleCheckLockSingle {// 使用volatile关键字确保instance在多线程环境下的可见性private volatile static DoubleCheckLockSingle instance;// 私有构造方法,避免外部直接实例化对象private DoubleCheckLockSingle() {// 私有构造方法}// 获取单例对象的静态方法public static DoubleCheckLockSingle getInstance() {// 第一次检查,避免不必要的同步if (instance == null) {// 同步块,确保在多线程环境下只有一个线程创建实例synchronized (DoubleCheckLockSingle.class) {// 第二次检查,防止多个线程同时进入同步块后重复创建实例if (instance == null) {instance = new DoubleCheckLockSingle();}}}return instance;}
}
  • 优点:

优点:

  1. 延迟加载:只有在需要时才创建实例,避免了资源的浪费。
  2. 线程安全:通过双重检查锁机制,在多线程环境下保证了线程安全性。
  3. 性能较好:相比简单的懒汉式单例,双重检查锁在保证线程安全的同时减少了不必要的同步开销。
  • 缺点:
  1. 实现复杂:双重检查锁的实现较为复杂,需要考虑到多线程并发情况下的各种细节,容易出错。
  2. 可能存在指令重排问题:在某些情况下,由于指令重排的影响,可能会导致获取到未完全初始化的实例。
  3. 不适用于早期JDK版本:在早期的JDK版本中,由于JVM对volatile关键字的实现问题,可能会导致DCL失效。

在实际应用中,双重检查锁单例模式适用于需要延迟加载且对性能要求较高的情况,但需要谨慎考虑线程安全和实现复杂度。

静态内部类
public class StaticInnerClazzSingle {// 私有化构造方法,避免外部直接实例化private StaticInnerClazzSingle() {// 初始化操作}// 静态内部类,利用类加载机制实现延迟加载private static class SingletonHolder {private static final StaticInnerClazzSingle INSTANCE = new StaticInnerClazzSingle();}// 获取单例实例的方法public static StaticInnerClazzSingle getInstance() {return SingletonHolder.INSTANCE;}
}
  • 优点
  1. 线程安全:利用类加载机制保证了线程安全,无需额外的同步措施。
  2. 延迟加载:只有在 getInstance() 方法被调用时才会加载内部类并初始化单例对象,实现了延迟加载。
  3. 简洁高效:代码简洁清晰,利用静态内部类的特性实现单例,高效且易于理解。
  • 缺点
  1. 不适用于非静态字段初始化:静态内部类只能访问外部类的静态成员,无法直接访问外部类的非静态成员。
  2. 可能过于晦涩:对于不熟悉静态内部类机制的开发人员,可能会造成理解上的困难。
  3. 无法传递参数:静态内部类实现的单例无法传递参数,因为在类加载时就初始化实例。

总体来说,静态内部类实现的单例模式是一种优雅且高效的实现方式,适合大多数情况下的单例需求,尤其适用于需要延迟加载且对线程安全有要求的场景。

枚举式单例模式
枚举类型是线程安全的,且只会加载一次。设计者充分的利用的这个特性来实现单例模式。且枚举模式不会被破坏,即不会因为反射而破坏这个实现
public enum EnumSingle {INSTANCE; // 单例实例// 枚举类的构造方法默认私有,保证外部无法实例化private EnumSingle() {// 初始化操作}// 获取单例实例的方法public static EnumSingle getInstance() {return INSTANCE;}
}
  • 优点:
  1. 线程安全:枚举类型在Java中天然线程安全,保证了单例的唯一性。
  2. 避免反射和序列化问题:枚举类型在序列化和反序列化过程中会自动处理实例化,避免反射攻击和序列化破坏单例的问题。
  3. 防止多次实例化:枚举类只能被实例化一次,避免了多次实例化的可能性。
  • 缺点:
  1. 无法懒加载:枚举类在类加载时就会被实例化,无法实现延迟加载。
  2. 无法传递参数:枚举类的构造方法无法传递参数,因为枚举实例在类加载时就被初始化。
  3. 不灵活:枚举类型本质上是类,无法继承其他类或实现其他接口,限制了扩展性。

总体来说,枚举方式实现单例模式是一种简洁高效且安全的方式,适合大多数单例需求,特别是在需要线程安全和避免反射攻击的情况下。然而,如果需要延迟加载或传递参数,枚举方式可能不适用。

存在的问题和解决方案

  1. 破坏单例模式
    除了枚举模式外,可以使用序列化和反射可以破坏单例模式。
    序列化原理: 反序列化读取对象是对象的copy。
    序列化和反序列化demo比较麻烦,笔者就不贴代码了,有兴趣的小伙伴可以自己尝试下。
/*** 反射破坏单例 以StaticInnerClazzSingle为例* 1. 获取StaticInnerClazzSingle类的Class对象。* 2. 获取该类的无参构造函数,并设置其可访问性。* 3. 通过构造函数创建一个新的HungrySingle实例。*/// 获取StaticInnerClazzSingle类的Class对象Class clazz = StaticInnerClazzSingle.class; // 获取类的无参构造器Constructor cons = clazz.getDeclaredConstructor(); // 设置构造器可访问cons.setAccessible(true); // 利用构造器创建实例StaticInnerClazzSingle single = (StaticInnerClazzSingle) cons.newInstance(); 
  1. 解决方法
// 当进行反序列化时,会自动调用该方法,将方法的返回值返回
public Object readResolve() {return SingletonHolder.INSTANCE;
}
// 反射破坏解决办法
private static boolean flag = false// 私有构造方法
private StaticInnerClazzSingle() {if (flag) {throw new RuntimeException("不能创建多个异常");}// 将flag设置为trueflag = true}
典型的单例模式

Runtime 饿汉式单例模式

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

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

相关文章

辅助功能IOU(交并比)_3.2

实现两个目标框的交并比候选框在多目标跟踪中的表达方式及相应转换方法 IOU(Intersection over Union),“交并比”,是计算机视觉和图像处理中常用的一个评价指标,尤其在目标检测任务中用来衡量模型预测的目标框与真实目标框的重合程度。 具体…

(附源码)基于Spring Boot + Vue的招聘平台设计与实现

前言 💗博主介绍:✌专注于Java、小程序技术领域和毕业项目实战✌💗 👇🏻 精彩专栏 推荐订阅👇🏻 2024年Java精品实战案例《100套》 🍅文末获取源码联系🍅 &#x1f31…

服务消费微服务

文章目录 1.示意图2.环境搭建1.创建会员消费微服务模块2.删除不必要的两个文件3.检查父子模块的pom.xml文件1.子模块2.父模块 4.pom.xml 添加依赖(刷新)5.application.yml 配置监听端口和服务名6.com/sun/springcloud/MemberConsumerApplication.java 创…

【windows】安装 Tomcat 及配置环境变量

👨‍🎓博主简介 🏅云计算领域优质创作者   🏅华为云开发者社区专家博主   🏅阿里云开发者社区专家博主 💊交流社区:运维交流社区 欢迎大家的加入! 🐋 希望大家多多支…

【学习】Python库addict,使用Dict 的类

from addict import Dict 这行代码导入了 Dict 类,它来自于 addict 模块。在这个上下文中,addict 是一个 Python 库,它提供了一个名为 Dict 的类,用于创建可通过属性访问的字典对象。 使用 addict 中的 Dict 类可以方便地创建字典…

【大模型】VS Code(Visual Studio Code)上安装的扩展插件用不了,设置VS Code工作区信任

文章目录 一、找到【管理工作区信任】二、页面显示处于限制模式,改为【信任】三、测试四、总结 【运行环境】win 11 相关文章: 【大模型】直接在VS Code(Visual Studio Code)上安装CodeGeeX插件的过程 【问题】之前在 VS Code上安装 CodeGeeX 插件后&…

Qt创建窗口选择的三个父类介绍 ----- QWidget、QMainWindow、QDialog

QWidget类 简介 QWidget是Qt中所有用户界面元素的基类。它提供了窗口的基本功能,并允许用户自定义窗口的外观和行为。QWidget可以包含其他QWidget子类的子窗口,从而实现复杂的用户界面。 特性 提供了窗口的基本功能,包括绘制、事件处理、…

Linux命令学习入门

文章目录 登录注销关机重启Vim编辑器快捷键文件目录类打包、解包、压缩和解压指令输出重定向>和追加>>指令时间日期类搜索查找类用户管理文件所有者所在组权限管理变更权限crond任务时间调度crond相关指令:特殊符号说明: at定时任务磁盘分区磁盘…

作用域闭包

一、闭包的概念 当函数可以记住并访问所在的词法作用域时,就产生了闭包,即使函数时在当前词法作用域之外执行。 下面用一些代码来解释这个定义。 function foo(){var a 2;function bar() {console.log(a);}bar(); }foo();这段代码看起来和嵌套作用域…

记录三菱:Works2-FB块

创建一个FB块,启保停,定义输入输出引脚,注意这里的数据类型是Bit 打开主程序,将FB块拖出来 启保停:加入时间设定,时间显示倒着

nginx集群部署访问不了怎么解决

如果你的Nginx集群部署无法访问,可能有多种原因导致,以下是一些常见的解决方法: 检查网络连接:确保服务器之间的网络连接是正常的,可以通过ping命令或telnet命令检查服务器之间的网络连通性。 检查防火墙设置&#xff…

docker基础(四)之docker run(第一弹)

目录 概述语法命令选项用法速查OPTIONS说明:-a stdinSTDIN/STDOUT/STDERR指的是什么?举例将容器的标准输入、标准输出、标准错误重定向到本地文件将容器的标准流重定向到设备将容器的标准流重定向到网络 -d示例应用场景返回的容器ID作用怎么通过容器ID查…

基于C/C++的easyx实现贪吃蛇游戏

文章目录: 一:运行效果 1.演示 2.思路和功能 二:代码 文件架构 Demo 必备知识:基于C/C的easyx图形库教程 一:运行效果 1.演示 效果图◕‿◕✌✌✌ 基于C/C的easyx实现贪吃蛇游戏运行演示 参考:【C语…

5.5.5、【AI技术新纪元:Spring AI解码】使用PGvector设置向量存储及进行相似性搜索

使用PGvector设置向量存储及进行相似性搜索 本节指导您如何设置PGvector VectorStore来存储文档嵌入并执行相似性搜索。 PGvector是一个开源的PostgreSQL扩展,能够支持存储和搜索机器学习生成的嵌入向量,提供查找精确和近似最近邻的功能。它设计得与PostgreSQL的其他特性无…

Oracle:ORA-01830错误-更改数据库时间格式

1,先把报错SQL语句拿出来执行,看看是不是报的这个错 ORA-01830: 日期格式图片在转换整个输入字符串之前结束 2,然后查看默认日期格式是不是“YYYY-MM-DD HH24:MI:SS”(正确格式)。; 执行: SELECT * FRO…

citus的快速开始

准备 dockercitus最新版本(docker pull citusdata/citus) docker网络 docker network create --subnet172.72.9.0/24 citus-test docker network ls启动citus服务 启动协调节点 docker run -dit --name citus-cod -p 5433:5432 -e POSTGRES_PASSWOR…

Transformer的前世今生 day06(Self-Attention和RNN、LSTM的区别)

Self-Attention和RNN、LSTM的区别 RNN的缺点:无法做长序列,当输入很长时,最后面的输出很难参考前面的输入,即长序列会缺失上文信息,如下: 可能一段话超过50个字,输出效果就会很差了 LSTM通过忘…

【SAP-ABAP】CO01保存时错误DBSQL_DUPLICATE_KEY_ERROR

找到该表的主键OBJNR,事务代码SM56中查看当前缓冲到该key的号码段,事务代码SNRO修改对象名称OBJNR编号范围状态。 事务代码SM13查看数据更新记录

静态路由实验配置

题目及实验图 第一步划分IP IP配置如下 1&#xff09;R1&#xff1a; <Huawei>sy Enter system view, return user view with CtrlZ. [Huawei]sy R1 [R1]int g 0/0/0 [R1-GigabitEthernet0/0/0]ip ad 192.168.1.1 30 [R1-GigabitEthernet0/0/1]ip ad 192.168.1.5…

LeetCode 热题 HOT 100(P21~P30)

系列文章&#xff1a; LeetCode 热题 HOT 100(P1~P10)-CSDN博客 LeetCode 热题 HOT 100(P11~P20)-CSDN博客 LeetCode 热题 HOT 100(P21~P30)-CSDN博客 LC48rotate_image . - 力扣&#xff08;LeetCode&#xff09; 题目&#xff1a; 给定一个 n n 的二维矩阵 matrix 表…