Java设计模式之单例模式(多种实现方式)

虽然写了很多年代码,但是说真的对设计模式不是很熟练,虽然平时也会用到一些,但是都没有深入研究过,所以趁现在有空练下手

这章主要讲单例模式,也是最简单的一种模式,但是因为spring中bean的广泛应用,所以现在单例模式在应用中其实很少会手动实现

在Spring中,默认情况下,Bean 是单例的,这意味着 Spring 容器会在第一次请求该 Bean 时创建一个实例,并且在整个应用程序的生命周期中保持该实例的单一性。换句话说,每次从 Spring 容器中请求相同的 Bean 时,都会得到相同的实例。

单例模式是一种常见的设计模式,适用于以下情况:

1.资源共享:当系统中需要共享某个资源(如数据库连接池、线程池、配置信息等)的时候,可以使用单例模式确保全局只有一个实例,避免资源的重复创建和浪费。

2.对象缓存:在需要频繁创建和销毁对象的情况下,可以使用单例模式将对象缓存起来,提高性能。

3.线程池:线程池通常被设计为单例,以确保在整个应用程序中只有一个线程池实例,用于管理线程的生命周期和执行任务。

4.日志对象:在系统中使用单例模式创建日志对象,可以确保所有的日志信息被统一记录,避免出现混乱的日志信息。

5.配置文件对象:将系统中的配置信息封装到单例对象中,可以方便地进行读取和修改。

6.对话框、窗口等界面组件:在图形用户界面(GUI)程序中,通常只需要一个对话框或窗口实例,可以使用单例模式确保全局只有一个实例。

7.管理器类:例如线程管理器、事件管理器等,这些管理器类通常被设计为单例,以便在整个系统中统一管理资源和事件。

总之,任何需要在系统中全局唯一存在的对象,且需要被频繁访问和共享的情况下,都可以考虑使用单例模式。

首先是最简单实用的饿汉模式

优点:

1.线程安全: 饿汉模式在类加载时就创建实例,并且实例是静态的 final 变量,因此在多线程环境下是线程安全的,不需要额外的线程同步控制。

2.简单易用: 饿汉模式的实现非常简单,通过静态变量初始化的方式就可以保证实例的唯一性和全局可访问性,不需要复杂的代码结构。

3.无需考虑懒加载和线程安全问题: 由于实例是在类加载时就创建好的,所以不需要考虑懒加载和线程安全问题,避免了相关的复杂性。

4.性能较好: 因为实例是在类加载时就创建好的,所以在获取实例时无需进行额外的判断和同步操作,性能较好。

缺点:

1.资源浪费: 饿汉模式在应用程序启动时就创建实例,并且实例是在整个应用程序生命周期内存在的,可能会导致资源的浪费。特别是如果实例占用大量资源或者需要较长时间进行初始化,可能会影响应用程序的启动速度。

2.不支持延迟加载: 饿汉模式不支持延迟加载,因为实例是在类加载时就创建好的,无法根据需要进行延迟加载。

3.可能导致类加载较慢: 如果一个类的实例创建比较耗时,那么在类加载时就会导致类加载较慢,影响整个应用程序的启动速度。

综上所述,饿汉模式适用于对性能要求较高,且实例创建比较简单且资源消耗较小的情况下。但是需要注意可能存在的资源浪费问题,特别是对于大型对象或者需要耗时初始化的实例。

直接上代码(建议使用)

public class EagerSingleton {// 在类加载时就创建实例,并初始化为静态变量private static final EagerSingleton instance = new EagerSingleton();// 私有化构造方法,防止外部实例化private EagerSingleton() {}// 获取单例实例的方法public static EagerSingleton getInstance() {try {// 模拟处理业务逻辑耗时Thread.sleep(5);} catch (InterruptedException e) {throw new RuntimeException(e);}return instance;}public static void main(String[] args) {for (int i= 0; i < 100; i++) {new Thread(() -> {System.out.println(EagerSingleton.getInstance().hashCode());}).start();}}
}

执行一下
可以看到hashCode都是一样的,这里有人可能会说hashCode一样并不代表对象一样,我只能说你的确是对的,但这不在本章讲解范围内

在这里插入图片描述

然后我们再来看一下懒汉模式

优点:

1.延迟加载(Lazy Loading): 懒汉模式在首次访问时才会创建实例,避免了在程序启动时就创建对象实例,节省了内存和系统资源。

2.节省资源: 因为实例是在需要时才创建的,所以在大部分情况下不会占用额外的资源。

3.线程安全问题相对简单: 在单线程环境下,懒汉模式不需要额外的线程同步机制来保证线程安全,实现简单。

缺点:

1.线程安全性问题: 在多线程环境下,懒汉模式可能存在线程安全问题。当多个线程同时调用 getInstance() 方法时,如果没有进行额外的线程同步处理,可能会导致创建多个实例。

2.性能问题: 在并发环境下,由于需要额外的线程同步控制,懒汉模式的性能可能会受到一定影响。例如,使用双重检查锁(Double-Checked Locking)来确保线程安全性,会增加额外的开销。

3.可能存在反序列化问题: 当类实现了 Serializable 接口,并且对象被序列化然后再反序列化时,如果没有正确地处理单例对象,可能会破坏单例的约束,导致出现多个实例。

4.不适用于高并发场景: 在高并发场景下,频繁调用 getInstance() 方法可能会导致性能瓶颈,因为所有线程都需要竞争同一个锁来获取实例。

综上所述,懒汉模式适用于单线程环境或者对性能要求不是非常高的场景,但在多线程环境下需要特别注意线程安全性问题,并且需要针对性能做出权衡。

直接上代码(不建议使用)

public class EagerSingleton {// 在类加载时就创建实例,并初始化为静态变量private static final EagerSingleton instance = new EagerSingleton();// 私有化构造方法,防止外部实例化private EagerSingleton() {}// 获取单例实例的方法public static EagerSingleton getInstance() {try {// 模拟处理业务逻辑耗时Thread.sleep(5);} catch (InterruptedException e) {throw new RuntimeException(e);}return instance;}public static void main(String[] args) {for (int i= 0; i < 100; i++) {new Thread(() -> {System.out.println(EagerSingleton.getInstance().hashCode());}).start();}}
}

执行一下
发现没有,hashCode都不一样,虽然hashCode相同不能证明对象是同一个,但是hashCode不相同肯定不是同一个对象,这说明其实是线程不安全的,因此这种写法其实是被淘汰了的

在这里插入图片描述

上面的那种写法虽然不推荐使用,但是提供了一种思路,就是只在需要的时候才加载,其主要目的还是为了节省资源(现在的硬件其实都很强大,这点资源省不省问题其实不大,这也让我想起了很多年前我刚入行还在写C++,当时问我师父说这个指针要是忘了释放怎么办,他跟我说没关系的,现在电脑都很牛逼,这点资源浪费根本影响不了什么)

好了下面我们来完善一下懒汉模式,最简单的方法就是使用synchronized关键字来保证线程的安全,当然同时也就伴随着性能的损耗(不推荐使用)
这里直接用synchronized关键字锁整个方法

public class LazySingleton {// 注意:volatile关键字是必须的,防止指令重排序private static volatile LazySingleton instance;// 私有化构造方法,防止外部实例化private LazySingleton() {}// 获取单例实例的方法public static synchronized LazySingleton getInstance() {// 在第一次调用时才创建实例if (instance == null) {try {// 模拟处理业务逻辑耗时Thread.sleep(5);} catch (InterruptedException e) {throw new RuntimeException(e);}instance = new LazySingleton();}return instance;}public static void main(String[] args) {for (int i= 0; i < 100; i++) {new Thread(() -> {System.out.println(LazySingleton.getInstance().hashCode());}).start();}}
}

执行一下

在这里插入图片描述

上面那个示例虽然保证了一个实例,但是性能上还是不如意,如是再优化一下就出现了下面这种(不是很推荐,因为看起来很复杂)
这里只在需要的地方加synchronized,就不再锁整个方法,性能上提示了一丢丢(注意这里其实是双重判断的懒汉模式,还有一个只有一层判断,因为和一开始的那个一样存在线程安全问题这里不做展示)

public class LazySingleton {// 注意:volatile关键字是必须的,防止指令重排序private static volatile LazySingleton instance;// 私有化构造方法,防止外部实例化private LazySingleton() {}// 获取单例实例的方法public static LazySingleton getInstance() {// 在第一次调用时才创建实例if (instance == null) {synchronized (LazySingleton.class) {// 注意这里使用的是双重判断,防止多线程并发时重复创建实例// 如果不加下面这个判断,多线程并发时,可能会创建多个实例if (instance == null) {try {// 模拟处理业务逻辑耗时Thread.sleep(5);} catch (InterruptedException e) {throw new RuntimeException(e);}instance = new LazySingleton();}}}return instance;}public static void main(String[] args) {for (int i = 0; i < 100; i++) {new Thread(() -> {System.out.println(LazySingleton.getInstance().hashCode());}).start();}}
}

通过比较可以发现,饿汉模式是不管需不需要都会创建一个实例,有点浪费资源,然后在程序启动的时候会拖慢一点速度。懒汉模式虽然是在需要的时候才创建实例,但是因为使用了synchronized关键字,所以在使用的时候也会有性能问题。虽然问题都不大,但是有些完美主义可能就接受不了,所以下面我们再优化一下。

直接上代码(建议使用,目前来看应该是最完美的实现方式,唯一的缺点就是不能反序列化)
由于静态内部类只有在被使用的时候才会被加载,所以单例实例的创建会延迟到 getInstance() 方法被调用的时候。而且由于类加载过程是线程安全的,所以这种方式也是线程安全的。

public class Singleton {// 私有化构造方法,防止外部实例化private Singleton() {}// 静态内部类持有单例实例private static class SingletonHolder {private static final Singleton INSTANCE = new Singleton();}// 获取单例实例的方法public static Singleton getInstance() {try {// 模拟处理业务逻辑耗时Thread.sleep(5);} catch (InterruptedException e) {throw new RuntimeException(e);}return SingletonHolder.INSTANCE;}public static void main(String[] args) {for (int i = 0; i < 100; i++) {new Thread(() -> {System.out.println(Singleton.getInstance().hashCode());}).start();}}
}

执行一下

在这里插入图片描述

那能不能写一个更完美的,让它能反序列化呢?答案当然是可以的!
直接上代码(虽然看起来很牛逼,用起来也很牛逼,但是不建议使用,违背Java代码设计原则)

public enum EnumSingleton {INSTANCE;// 注意枚举不是类没有构造方法// 这里可以用来处理业务逻辑public void doSomething() {System.out.println("do something");}public static void main(String[] args) {for (int i = 0; i < 100; i++) {new Thread(() -> {System.out.println(EnumSingleton.INSTANCE.hashCode());}).start();}}
}

执行一下

在这里插入图片描述

还有些其它的方式就不讲了,这几种基本就是最常见的,大家根据实际业务情况自行选择就好

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

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

相关文章

YoloV8改进策略:BackBone改进|PKINet

摘要 PKINet是面向遥感旋转框的主干,网络包含了CAA、PKI等模块,给我们改进卷积结构的模型带来了很多启发。本文,使用PKINet替代YoloV8的主干网络,实现涨点。PKINet是我在作者的模型基础上,重新修改了底层的模块,方便大家轻松移植到YoloV8上。 论文:《Poly Kernel Ince…

计算机三级网络技术 选择+大题234笔记

上周停去准备计算机三级的考试啦&#xff0c;在考场上看到题目就知道这次稳了&#xff01;只有一周的时间&#xff0c;背熟笔记&#xff0c;也能稳稳考过计算机三级网络技术&#xff01;

鸿蒙开发学习:【华为支付服务客户端案例】

简介 华为应用内支付服务&#xff08;HUAWEI In-App Purchases&#xff09;支持3种商品&#xff0c;包括消耗型商品、非消耗型商品和订阅型商品。 消耗商品&#xff1a;仅能使用一次&#xff0c;消耗使用后即刻失效&#xff0c;需再次购买。非消耗商品&#xff1a;一次性购买…

计算机常见的知识点(3)

计算机系统 系统的构成 一个完整的计算机系统是由硬件和软件组成 硬件是由运算器、控制器、存储器、输入设备、输出设备五部分组成 其中&#xff1a;中央处理器(简称CPU)运算器控制器 主机中央处理器主存储器 计算机软件包括计算机本身运行所需要的系统软件和用户完成任务…

Mybatis中显示插入数据成功,但在数据库中却没有显示插入的数据

1、在mybatis-config.xml中查看是否添加了JDBC&#xff0c;并引入了映射文件 2、在测试文件中&#xff0c;结尾是否添加提交事务&#xff1a;sqlSession.commit() 添加了这一步就能够将数据提交到数据库中&#xff0c;最后再关闭事务&#xff1a;sqlSession.close() * 如果运…

JWT原理分析

为什么会有JWT的出现&#xff1f; 首先不得不提到一个知识叫做跨域身份验证&#xff0c;JWT的出现就是为了更好的解决这个问题&#xff0c;但是在没有JWT的时候&#xff0c;我们一般怎么做呢&#xff1f;一般使用Cookie和Session&#xff0c;流程大体如下所示&#xff1a; 用…

手撕算法-买卖股票的最佳时机 II(买卖多次)

描述 分析 使用动态规划。dp[i][0] 代表 第i天没有股票的最大利润dp[i][1] 代表 第i天持有股票的最大利润 状态转移方程为&#xff1a;dp[i][0] max(dp[i-1][0], dp[i-1][1] prices[i]); // 前一天没有股票&#xff0c;和前一天有股票今天卖掉的最大值dp[i][1] max(dp[i-1…

Linux查看磁盘空间

查看磁盘空间 df -h 查看目录所占空间 du -sh [目录] 查看当前目录下, 所有目录所占空间 (一级目录) find . -maxdepth 1 -type d -exec du -sh {} \;-maxdepth 1 查看的目录深度是1级, 2则是2级

FOCUS-AND-DETECT: A SMALL OBJECTDETECTION FRAMEWORK FOR AERIAL IMAGES

摘要 为了解决小对象检测问题&#xff0c;提出了一个叫做 Focus-and Detect 的检测框架&#xff0c;它是一个两阶段的框架。 第 一阶段包括由高斯混合模型监督的对象检测器网络&#xff0c;生成构成聚焦区域的对象簇 。 第二阶段 也是一个物体探测器网络&#xff0c;预测聚焦…

【云开发笔记No.6】腾讯CODING平台

腾讯云很酷的一个应用&#xff0c;现在对于研发一体化&#xff0c;全流程管理&#xff0c;各种工具层出不穷。 云时代用云原生&#xff0c;再加上AI&#xff0c;编码方式真是发生了质的变化。 从前&#xff0c;一个人可以写一个很酷的软件&#xff0c;后来&#xff0c;这变得…

<商务世界>《第16课 餐桌礼仪之座次》

1 简要 我国自古以来就很重视座位礼仪&#xff0c;非讲究&#xff0c;分君臣、分宾主、分方位等等而今座位礼仪已经简化为&#xff1a; 以“中”为尊&#xff1a; 中心为尊&#xff0c;突出主位。 以“右”为尊&#xff1a; 从历史上到国际上都是以右为尊。 以“内”为尊&…

故障诊断模型 | 基于图卷积网络的轴承故障诊断

文章目录 文章概述模型描述模型描述参考资料文章概述 故障诊断模型 | 基于图卷积网络的轴承故障诊断 模型描述 针对基于图卷积网络(GCN)的故障诊断方法大多默认节点间的权重相同、导致诊断精度较低与鲁棒性较差的问题,提出了一种基于欧式距离和余弦距离的 GCN 故障诊断方法…

力扣热门算法题 62. 不同路径,66. 加一,67. 二进制求和

62. 不同路径&#xff0c;66. 加一&#xff0c;67. 二进制求和&#xff0c;每题做详细思路梳理&#xff0c;配套Python&Java双语代码&#xff0c; 2024.03.21 可通过leetcode所有测试用例。 目录 62. 不同路径 解题思路 完整代码 Python Java 66. 加一 解题思路 …

29-goto语句

29-1 goto语句介绍 C语言中提供了可以随意滥用的goto语句和标记跳转的标号。 从理论上goto语句是没有必要的&#xff0c;实践中没有goto语句也可以很容易的写出代码。 但是某些场合下goto语句还是用得着的&#xff0c;最常见的用法就是终止程序在某些深度嵌套的结构的处理过程…

第十一届蓝桥杯大赛第二场省赛试题 CC++ 研究生组-回文日期

solution1&#xff08;通过50%&#xff09; #include<stdio.h> void f(int a){int t a;while(a){printf("%d", a % 10);a / 10;}if(t < 10) printf("0"); } int isLeap(int n){if(n % 400 0 || (n % 4 0 && n % 100 ! 0)) return 1;r…

抖音IP属地怎么更改

抖音是一个非常受欢迎的短视频平台&#xff0c;吸引了无数用户在上面分享自己的生活和才艺。然而&#xff0c;随着快手的火爆&#xff0c;一些用户开始担心自己的IP地址会被他人获取&#xff0c;引起个人隐私风险。那么&#xff0c;抖音用户又该如何更改到别的地方呢&#xff1…

父类子类构造方法调用示例

父类写无参构造&#xff0c;子类不写构造&#xff0c;实例化子类&#xff0c;会同时调用父类构造方法 public class Father {private String name;private int age;public Father() {System.out.println("父类无参构造");}} public class Son extends Father {priva…

私密个人相册 Android 藏起来你的照片和视频

珍藏每一刻 软件介绍版本号 1.0.0更新日期 2024-03-20 在数字时代&#xff0c;每个人都是自己故事的摄影师。私密个人相册应用&#xff0c;专为珍视隐私与回忆的你设计。我们提供一个安 全的空间&#xff0c;让你的每一张照片和每一个视频都得到妥善保护&#xff0c;同时也能…

通过dbeaver链接dm8数据库

一、环境说明 windows 11 vmware 17 ubuntu 22 tt:~$ lsb_release -a No LSB modules are available. Distributor ID: Ubuntu Description: Ubuntu 22.04.3 LTS Release: 22.04 Codename: jammytt:~$ docker info Client:Version: 24.0.5Context: d…

C++之移动语义与智能指针

目录 移动语义 1、几个基本概念的理解 2、复制控制语义的函数 3、移动控制语义的函数 3.1、移动构造函数: 3.2、移动赋值函数 4.区别 5、std::move函数 6.代码演示: 资源管理与智能指针 一、C语言中的问题 二、C的解决办法(RAII技术)&#xff1a; 三、四种智能指针…