单例模式五种写法

单例模式五种写法

单例模式有五种写法:饿汉、懒汉、双重检验锁、静态内部类、枚举.

单例模式属于设计模式中的创建型模式

一、单例模式应用场景

  • windows的task manager(任务管理器)就是很典型的单例模式;

  • windows的recycle bin(回收站)也是典型的单例应用,在整个系统运行过程中,回收站一直维护仅有的一个实例;

  • 项目中,读取配置文件的类,一般也只有一个对象,没有必要每次使用配置文件数据,每次new一个对象去读取;

  • 应用程序的日志应用,一般都可用单例模式实现,这一般是由于共享的日志文件一直处于打开状态,因为只能有一个实例去操作,否则内容不好追加;

  • 数据库连接池的设计一般也是采用单例模式,因为数据库连接是一种数据库资源;

  • 在spring中,每个bean默认就是单例的,这样做的优点是spring容器可以管理;

  • 在servlet编程中,每个servlet也是单例;

  • 在spring mvc框架,控制器对象也是单例;

单例模式算是设计模式中最容易理解,也是最容易手写代码的模式了。但是其中的坑却不少,所以也常作为面试题来考,本文主要对几种单例写法的整理,并分析其优缺点。

二、合格的单例模式要求

合格的单例模式的实现,至少要保证以下三点:

1. 实现单例功能;
2. 延迟加载;
3. 并发时不出错。 

三、五种单例模式的实现

我们将创建一个 SingleObject 类。SingleObject 类有它的私有构造函数和本身的一个静态实例。

SingleObject 类提供了一个静态方法,供外界获取它的静态实例。SingletonPatternDemo 类使用 SingleObject 类来获取 SingleObject 对象。

image-20240408234756088

1. 饿汉式

这种方式非常简单,因为单例的实例被声明成 static 和 final 变量了,在第一次加载类到内存中时就会初始化,所以创建实例本身是线程安全的

public class Singleton {private static final Singleton instance = new Singleton();private Singleton() {}public static Singleton getInstance() {return instance;}
}
  • 饿汉模式的缺点:可能在还不需要此实例的时候就已经把实例创建出来了,没起到 lazy loading 效果,空间换时间:浪费内存。
  • 饿汉模式的优点:实现简单,线程安全,调用效率高。

2. 懒汉式

public class Singleton {private static Singleton instance;private Singleton() {}public static Singleton getInstance() {if (instance == null) {instance = new Singleton();}return instance;}
}
  • 懒汉模式的优点:弥补了饿汉模式的缺点,起到了 lazy loading 的效果,时间换空间:节约内存。
  • 懒汉模式的缺点:多线程并发时有线程安全问题,有可能创建多个实例,也就是说在多线程下不能正常工作。

3. 双重检验锁

双重检验锁模式(double checked locking pattern),是一种使用同步块加锁的方法。

称其为双重检查锁,因为会有两次检查 instance == null,一次是在同步块外,一次是在同步块内。

public class Singleton {private static Singleton instance;private Singleton() {}public static Singleton getInstance() {if (instance == null) { //第一次检查,提高性能,避免所有的线程都去加锁和释放锁synchronized (Singleton.class) {if (instance == null) { //第二次检查,确保只有一个线程创建实例instance = new Singleton();}}}return instance;}
}

为什么在同步块内还要再检验一次?因为可能会有多个线程一起进入同步块外的 if,如果在同步块内不进行二次检验的话就会生成多个实例了

这段代码看起来很完美,很可惜,它是有问题的。**主要在于instance = new Singleton()这句,这并非是一个原子操作,事实上在 JVM 中这句话大概做了下面 3 件事情: **

(1) 给 instance 分配内存(2) 调用 Singleton 的构造函数来初始化成员变量(3) 将instance对象指向分配的内存空间(执行完这步 instance 就为非 null 了)

但是在 JVM 的即时编译器(JIT)中存在指令重排序的优化。也就是说上面的第二步和第三步的顺序是不能保证的,最终的执行顺序可能是 1-2-3 也可能是 1-3-2。 如果是后者,则在 3 执行完毕、2 未执行之前,被线程二抢占了,这时 instance 已经是非 null 了(但却没有初始化),所以线程二会直接返回 instance,然后使用,然后顺理成章地报错。

解决办法: 我们只需要将 instance 变量声明成 volatile 修饰就可以了,其作用是防止指令重排序

public class Singleton {private volatile static Singleton instance; //声明成 volatileprivate Singleton (){}public static Singleton getInstance() {if (instance == null) {                         synchronized (Singleton.class) {if (instance == null) {       instance = new Singleton();}}}return instance;} 
}

使用 volatile 的主要原因是其另一个特性:禁止指令重排序优化。也就是说,在 volatile 变量的赋值操作后面会有一个内存屏障(生成的汇编代码上),读操作不会被重排序到内存屏障之前。比如上面的例子,取操作必须在执行完 1-2-3 之后或者 1-3-2 之后,不存在执行到 1-3 然后取到值的情况。从「先行发生原则」的角度理解的话,就是对于一个 volatile 变量的写操作都先行发生于后面对这个变量的读操作

  • 双校验懒汉模式的优点:弥补了懒汉模式的缺点,防止了并发问题。
  • 双校验懒汉模式的缺点:因为涉及到锁,因此性能有损耗;代码变得更复杂。

4. 静态内部类-登记式

静态内部类(static nested class)

使用静态内部类的方式,也是《Effective Java》上所推荐的。

public class Singleton {  private static class SingletonHolder {  private static final Singleton INSTANCE = new Singleton();  }  private Singleton (){}  public static final Singleton getInstance() {  return SingletonHolder.INSTANCE; }  
}

懒汉式的变种: 静态内部类 空间换时间

首先静态内部类不会随着外部类的加载而加载 ,只有静态内部类的静态成员被调用时才会进行加载也就是初始化 ,这个就是调用静态内部类的静态成员,然后在初始化的过程中new一个对象INSTANCE

这种写法仍然使用JVM本身机制保证了线程安全问题;由于 SingletonHolder 是私有的,除了 getInstance() 之外没有办法访问它,因此它是懒汉式的,同时读取实例的时候不会进行同步,没有性能缺陷;

  • 静态内部类单例的优点:线程安全,支持延迟加载,获取singleton对象时不需要加锁,使用方便。
  • 静态内部类单例的缺点:会增加类的数量,在第一次加载时可能会略微增加启动时间。

5. 枚举

用枚举写单例非常简单,这也是它最大的优点。下面这段代码就是声明枚举实例的通常做法:

public enum EasySingleton{INSTANCE;
}

我们可以通过EasySingleton.INSTANCE来访问实例,这比调用getInstance()方法简单多了。创建枚举默认就是线程安全的,所以不需要担心double checked locking,而且还能防止反序列化导致重新创建新的对象。

枚举单例优点:写法简单,实现效率高,因为枚举本身就是单例,由JVM从根本上提供保障,避免通过反射和反序列化的漏洞

枚举单例缺点:没有延迟加载,枚举实例在编译时就已经创建,无法在运行时延迟加载

四、总结

一般来说,单例模式有五种写法:饿汉、懒汉、双重检验锁、静态内部类、枚举

一般情况下,不建议使用第 2 种懒汉方式,建议使用第 1 种饿汉方式。只有在要明确实现 lazy loading 效果时,才会使用第 4 种静态内部类登记方式。如果涉及到反序列化创建对象时,可以尝试使用第 5 种枚举方式。如果有其他特殊的需求,可以考虑使用第 3 种双检锁方式。

  • 单例类的特点:
(1)单例类确保自己只有一个实例(2)单例类必须自己创建自己的实例(3)单例类必须为其他对象提供唯一的实例。
  • 单例类的优点:
(1) 控制资源的使用,通过线程同步来控制资源的并发访问。(2) 控制实例的产生数量,达到节约资源的目的。(3) 作为通信的媒介,数据共享。他可以在不建立直接关联的条件下,让多个不相关的两个线程或者多个进程之间实现通信。
  • 单例模式的主要缺点如下:
(1) 由于单例模式中没有抽象层,因此单例类的扩展有很大的困难。(2) 单例类的职责过重,在一定程度上违背了“单一职责原则”。因为单例类既充当了工厂角色,
提供了工厂方法,同时又充当了产品角色,包含一些业务方法,将产品的创建和产品的本身的功能融合到一起。

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

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

相关文章

Composer是什么?

Composer是PHP的一个依赖管理工具,它允许开发者声明项目所依赖的代码库,并在项目中自动安装这些依赖。它使用composer.json文件来定义项目的依赖关系,并使用composer.lock文件来锁定依赖的版本,以确保项目的稳定性和可重复性。 Co…

物联网的核心价值是什么?——青创智通

工业物联网解决方案-工业IOT-青创智通 物联网,这个词汇在当今的科技领域已经变得耳熟能详。但当我们深入探索物联网的核心价值时,我们会发现它远不止是一个简单的技术概念,而是一种能够彻底改变我们生活方式和工作方式的革命性力量。 物联网…

力扣周赛392复盘

3105. 最长的严格递增或递减子数组 题目 给你一个整数数组 nums 。 返回数组 nums 中 严格递增 或 严格递减 的最长非空子数组的长度。 思考: 返回什么:返回最长非空子数组的长度。return max(decs_len,incs_len); 但实际上我们只需要用一个变量ans就…

[leetcode] max-area-of-island

. - 力扣(LeetCode) 给你一个大小为 m x n 的二进制矩阵 grid 。 岛屿 是由一些相邻的 1 (代表土地) 构成的组合,这里的「相邻」要求两个 1 必须在 水平或者竖直的四个方向上 相邻。你可以假设 grid 的四个边缘都被 0(代表水&…

Java | Leetcode Java题解之第22题括号生成

题目&#xff1a; 题解&#xff1a; class Solution {static List<String> res new ArrayList<String>(); //记录答案 public List<String> generateParenthesis(int n) {res.clear();dfs(n, 0, 0, "");return res;}public void dfs(int n ,int…

牛客网刷题 | BC51 及格分数

描述 KiKi想知道他的考试分数是否通过&#xff0c;请帮他判断。从键盘任意输入一个整数表示的分数&#xff0c;编程判断该分数是否在及格范围内&#xff0c;如果及格&#xff0c;即&#xff1a;分数大于等于60分&#xff0c;是输出“Pass”&#xff0c;否则&#xff0c;输出“…

利用vite创建vue项目

创建vue项目步骤 打开HBuilder X工具&#xff0c;创建空白项目 进入终端(鼠标点击文件进行选择&#xff0c;然后终端) 利用vite脚手架创建项目 &#xff08;前提要将HBuilder X工具属性设为管理员运行状态&#xff08;属性》兼容》管理员身份运行此程序&#xff09; npm …

Ubuntu22.04配置ROS2+PX4仿真环境

Ubuntu22.04配置ROS2PX4仿真环境 主要参考源&#xff1a; https://blog.csdn.net/weixin_44174421/article/details/135827130 https://blog.csdn.net/Zecet/article/details/130474620 一、准备工作 确保网络能够连接到github&#xff0c;出错主要源自于此&#xff1b;确保…

【Qt 学习笔记】Qt常用控件 | 按钮类控件Check Box的使用及说明

博客主页&#xff1a;Duck Bro 博客主页系列专栏&#xff1a;Qt 专栏关注博主&#xff0c;后期持续更新系列文章如果有错误感谢请大家批评指出&#xff0c;及时修改感谢大家点赞&#x1f44d;收藏⭐评论✍ Qt常用控件 | 按钮类控件Check Box的使用及说明 文章编号&#xff1a;…

C# 两种方法截取活动窗口屏幕,实现窗体截图

方法1&#xff0c;截屏内容仅包括活动窗口界面&#xff0c;而方法2是从屏幕范围取图&#xff0c;截屏内容会包括屏幕上所有内容。例如有一些程序在桌面顶层显示半透明的悬浮窗&#xff0c;用方法2截屏就会包括这些内容&#xff0c;并不是单纯的活动窗口内容。 方法1&#xff0c…

解决 MSYS2 Qt 6.7 默认 stylesheet 在 windows 11 下的显示故障

项目场景&#xff1a; MSYS2 升级到 Qt6.7.0&#xff0c;发现显示故障&#xff0c;所有Qt6程序以及 QtCreator的SpinBox都显示不全&#xff0c;Combox的底色不对。 问题描述 2024年4月1日&#xff0c;pacman升级MSYS2后&#xff0c;Qt6遇到风格错误。如果使用官方的 Qt onlin…

【PostgreSQL里insert on conflict do操作时的冲突报错分析】

最近在巡检PostgreSQL的数据库的时候&#xff0c;发现部分数据库里存在大量的如下报错 ERROR: ON CONFLICT DO UPDATE command cannot affect row a second time HINT: Ensure that no rows proposed for insertion within the same command have duplicate constrained val…

Winform重难点笔记

FrmMain.cs 中的 partial&#xff08;部分的&#xff09; 和 FrmMain.Designer.cs 中的 partial 一样&#xff0c;不是一个类的修饰符&#xff0c;是限定这个类本身的组成部分&#xff0c;叫做部分类。当程序在编译和运行时&#xff0c;会把 FrmMain.cs 中的 FrmMain 类 和 Frm…

前端console用法分享

console对于前端人员来讲肯定都不陌生&#xff0c;相信大部分开发者都会使用console来进行调试&#xff0c;但它能做的绝不仅限于调试。 最常见的控制台方法 作为开发者&#xff0c;最常用的 console 方法如下&#xff1a; 控制台打印结果&#xff1a; 今天我分享的是一些 co…

RabbitMQ Stream插件使用详解

2.4版为RabbitMQ流插件引入了对RabbitMQStream插件Java客户端的初始支持。 RabbitStreamTemplateStreamListener容器 将spring rabbit流依赖项添加到项目中&#xff1a; <dependency><groupId>org.springframework.amqp</groupId><artifactId>sprin…

java-spring 图灵 04

在Spring框架中&#xff0c;可以使用org.springframework.core.io.support.ResourcePatternResolver接口的resolveBasePackage方法来将指定的基础包解析为用于包搜索路径的模式规范。 例如&#xff0c;如果基础包是com.example.app&#xff0c;则可以使用resolveBasePackage方法…

微信小程序-绘制图片并分享下载(painter)

1、引入painter插件 painter官网地址 1.1 可通过官网的方法引入painter插件&#xff0c; 官方插件下载地址 1.2 可下载本文附带的插件包直接引入 1.2.1 复制下载下来的文件中的painter文件夹&#xff0c;将其放在components目录下 1.2.2 页面中引入并使用 .json {"…

Mac版2024 CleanMyMac X 4.15.2 核心功能详解 cleanmymac这个软件怎么样?cleanmymac到底好不好用?

近些年伴随着苹果生态的蓬勃发展&#xff0c;越来越多的用户开始尝试接触Mac电脑。然而很多人上手Mac后会发现&#xff0c;它的使用逻辑与Windows存在很多不同&#xff0c;而且随着使用时间的增加&#xff0c;一些奇奇怪怪的文件也会占据有限的磁盘空间&#xff0c;进而影响使用…

如何在 VM 虚拟机中安装 OpenEuler 操作系统保姆级教程(附链接)

一、VMware Workstation 虚拟机 若没有安装虚拟机的可以参考下篇文章进行安装&#xff1a; 博客链接https://eclecticism.blog.csdn.net/article/details/135713915 二、OpenEuler 镜像 点击链接前往官网 官网 选择第一个即可 三、安装 OpenEuler 打开虚拟机安装 Ctrl …

家居网购项目(手写分页)

文章目录 1.后台管理—分页显示1.程序框架图2.编写数据模型Page.java 3.编写dao层1.修改FurnDao增加方法 2.修改FurnDaoImpl增加方法 3.单元测试FurnDaoTest 4.编写service层1.修改FurnService增加方法 2.修改FurnServiceImpl增加方法3.单元测试FurnServiceTest 5.编写DataUtil…