单例模式有几种写法?请谈谈你的理解?

为什么有单例模式?

单例模式(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(任务管理器)、回收站


使用时如何选择?

在这里插入图片描述
在实际业务场景中,可以根据具体需求选择适合的单例模式。如果需要在应用启动时创建对象,且对性能要求较高,可以选择饿汉式或双重校验锁;如果需要延迟加载对象,可以选择静态内部类或枚举单例模式;如果对线程安全要求较高,可以选择双重校验锁或静态内部类单例模式



如果有想要交流的内容欢迎在评论区进行留言,如果这篇文档受到了您的喜欢那就留下你点赞+收藏+评论脚印支持一下博主~

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

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

相关文章

测试用例的书写方式以及测试模板大全

一个优秀的测试用例&#xff0c;应该包含以下信息&#xff1a; 1 &#xff09; 软件或项目的名称 2 &#xff09; 软件或项目的版本&#xff08;内部版本号&#xff09; 3 &#xff09; 功能模块名 4 &#xff09; 测试用例的简单描述&#xff0c;即该用例执行的目的或方法…

SpringMVC实现对网页的访问,在请求控制器中创建处理请求的方法

目录 测试HelloWorld RequestMapping注解 RequestMapping注解的位置 RequestMapping注解的value属性 RequestMapping注解的method属性 SpringMVC支持路径中的占位符&#xff08;重点&#xff09; SpringMVC获取请求参数 1、通过ServletAPI获取 2、通过控制器方法的形参…

Spring-boot项目+Rancher6.3部署+Nacos配置中心+Rureka注册中心+Harbor镜像仓库+NFS存储

目录 一、项目概述二、环境三、部署流程3.1 Harbor部署3.1.1 docker安装3.1.2 docker-compose安装3.1.3 安装证书3.1.4 Harbor下载配置安装 3.2 NFS存储搭建3.3 Rancher平台配置3.3.1 NFS存储相关配置3.3.2 Harbor相关配置3.3.3 Nacos部署及相关配置3.3.4 工作负载deployment配…

Vue3+vite引入Tailwind CSS

Tailwind CSS 是一个为快速创建定制化 UI 组件而设计的实用型框架。与其他 CSS 框架或库不同&#xff0c;Tailwind CSS 组件没有预先设置好样式。可以使用 Tailwind 的低级实用类来为 CSS 元素设置样式&#xff0c;如 margin、flex、color 等。 自从 2017 年发布以来&#xff…

嵌入式学习第十五天

内存管理: 1.malloc void *malloc(size_t size); 功能: 申请堆区空间 参数: size:申请堆区空间的大小 返回值: 返回获得的空间的首地址 失败返回NULL 2.free void free(void *ptr); 功能: 释放堆区空间 注…

五大架构风格之一:数据流风格

数据流风格详细介绍 系统架构数据流风格是一种软件体系结构风格&#xff0c;它强调了系统内部不同部分之间的数据流动。这种风格侧重于描述系统中的数据处理过程&#xff0c;以及数据是如何从一个组件传递到另一个组件的。以下是系统架构数据流风格的详细介绍&#xff1a; 1 基…

vue3项目下载@element-plus/icons-vue苦笑不得的乌龙

一、背景 node.js版本&#xff1a;v16.20.1 npm版本&#xff1a;8.19.4 pnpm版本&#xff1a;8.0.0 二、心路历程 pnpm install element-plus/icons-vue 用命令下载element-plus/icons-vue的时候&#xff0c;报错并提醒如图 是&#xff0c;我按照提示执行了&#xff0c;结…

基于腾讯云自然语言处理 NLP服务实现文本情感分析

文章目录 一、前言二、NLP 服务简介三、Python 调用腾讯云 NLP 服务 SDK 构建情感分析处理3.1 开通腾讯云 NLP 服务3.2 创建的腾讯云持久证书&#xff08;如果已创建请跳过&#xff09;3.2 在腾讯云服务器中安装 Git 工具以及 Python 环境3.3 安装 qcloudapi-sdk-python3.4 部署…

JRT人大金仓测试

之前基于IRIS导出的Sql脚本用JRT的导表脚本执行Sql语句在PostGreSql数据库把IRIS导出的库还原。并且试了模板设计器的打开和保存及打印功能。本次测试IRIS导出的Sql在人大金仓上还原数据库&#xff0c;并且测试模板设计器功能和打印。 首先碰到的一个坑是人大金仓把空串存成NU…

【js逆向】scrapy基础

目录 一, 爬虫工程化 二, scrapy简介 三, Scrapy工作流程(重点) 四, scrapy安装 4.1 pip 安装 4.2 wheel安装 五, Scrapy实例 六, 自定义数据传输结构item 七, scrapy使用小总结 一, 爬虫工程化 在之前的学习中我们已经掌握了爬虫这门技术需要的大多数的技术点, 但是我…

LabVIEW传感器通用实验平台

LabVIEW传感器通用实验平台 介绍了基于LabVIEW的传感器实验平台的开发。该平台利用LabVIEW图形化编程语言和多参量数据采集卡&#xff0c;提供了一个交互性好、可扩充性强、使用灵活方便的传感器技术实验环境。 系统由硬件和软件两部分组成。硬件部分主要包括多通道数据采集卡…

代码随想录 Leetcode538. 把二叉搜索树转换为累加树

题目&#xff1a; 代码(首刷看解析 2024年1月31日&#xff09;&#xff1a; class Solution { public:int pre 0;TreeNode* convertBST(TreeNode* root) {if (!root) return nullptr;root->right convertBST(root->right);if (pre 0) {pre root->val;}else {root…

【百度Apollo】轨迹绘制:探索路径规划和可视化技术的应用

&#x1f3ac; 鸽芷咕&#xff1a;个人主页 &#x1f525; 个人专栏: 《linux深造日志》《粉丝福利》 ⛺️生活的理想&#xff0c;就是为了理想的生活! ⛳️ 推荐 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下…

2024.1.28 GNSS 学习笔记

1.基于 地球自转改正卫地距 以及 伪距码偏差 重构定位方程&#xff1a; 先验残差计算公式如下所示&#xff1a; 2.观测值如何定权&#xff1f;权重如何确定&#xff1f; 每个卫星的轨钟精度以及电离层模型修正后的误差都有差异&#xff0c;所以我们不能简单的将各个观测值等权…

Kafka-服务端-PartitionLeaderSelector、ReplicaStateMachine

PartitionLeaderSelector 通过对前面的分析可知&#xff0c;PartitionMachine将Leader副本选举、确定ISR集合的工作委托给了PartitionLeaderSelector接口实现&#xff0c;PartitionMachine可以专注于管理分区状态。这是策略模式的一种典型的应用场景。 图展示了PartitionLead…

房屋租赁系统-java

思维导图&#xff1a;业务逻辑 类的存放&#xff1a; 工具类 Utility package study.houserent.util; import java.util.*; /***/ public class Utility {//静态属性。。。private static Scanner scanner new Scanner(System.in);/*** 功能&#xff1a;读取键盘输入的一个菜单…

【行业应用-智慧零售】东胜物联餐饮门店智能叫号解决方案,为企业智能化升级管理服务

随着科技的不断进步&#xff0c;物联网设备已经广泛应用于各行各业&#xff0c;包括餐饮业。在餐饮门店的线下运营过程中&#xff0c;叫号系统是一项重要的设备需求。传统的叫号方式往往会消耗大量的人力和时间&#xff0c;而物联网技术为餐饮行业提供了一种更高效、智能化的解…

redis使用Big key的问题

文章目录 BigKey带来的问题业务场景具体现象解决思路 BigKey带来的问题 客户端执行命令的时延变大&#xff1a;对大Key进行的慢操作会导致后续的命令被阻塞&#xff0c;从而导致一系列慢查询。 引发操作阻塞&#xff1a;Redis内存达到maxmemory参数定义的上限引发操作阻塞或重…

前端Web开发

安装flask框架 pip install flask 导入flask模块 from flask import Flask 【可能遇到的问题】 出现了如下警告&#xff1a; WARNING: You are using pip version 21.2.4; however, version 22.0.4 is available.You should consider upgrading via the D:\Python\python…

德国布局离子阱量子计算机!德国电信与奥地利量子公司AQT达成合作

​内容来源&#xff1a;量子前哨&#xff08;ID&#xff1a;Qforepost&#xff09; 编辑丨慕一 编译/排版丨琳梦 璧园 深度好文&#xff1a;1000字丨8分钟阅读 近期&#xff0c;德国电信宣布&#xff0c;其子公司T-Systems与奥地利量子计算公司Alpine Quantum Technologies …