设计模式系列:单例模式

 作者持续关注WPS二次开发专题系列,持续为大家带来更多有价值的WPS开发技术细节,如果能够帮助到您,请帮忙来个一键三连,更多问题请联系我(WPS二次开发QQ群:250325397),摸鱼吹牛嗨起来!

定义

单例(Singleton)模式的定义:指一个类只有一个实例,且该类能自行创建这个实例的一种模式。

特点

  1. 单例类只有一个实例对象;
  2. 该单例对象必须由单例类自行创建;
  3. 单例类对外提供一个访问该单例的全局访问点。

使用场景

单例模式可以保证在一个 JVM 中只存在单一实例。单例模式的应用场景主要有以下几个方面。

  • 需要频繁创建的一些类,使用单例可以降低系统的内存压力,减少 GC。
  • 某类只要求生成一个对象的时候,如一个班中的班长、每个人的身份证号等。
  • 某些类创建实例时占用资源较多,或实例化耗时较长,且经常使用。
  • 某类需要频繁实例化,而创建的对象又频繁被销毁的时候,如多线程的线程池、网络连接池等。
  • 频繁访问数据库或文件的对象。
  • 对于一些控制硬件级别的操作,或者从系统上来讲应当是单一控制逻辑的操作,如果有多个实例,则系统会完全乱套。
  • 当对象需要被共享的场合。由于单例模式只允许创建一个对象,共享该对象可以节省内存,并加快对象访问速度。如 Web 中的配置对象、数据库的连接池等。

模式结构

单例模式的主要角色如下。

  • 单例类:包含一个实例且能自行创建这个实例的类。
  • 访问类:使用单例的类。

具体实现

(1) 饿汉式--(线程安全,实用)

/*** 单例模式--单例模式的饿汉式(线程安全,可用)* <pre>* (1)私有化该类的构造函数* (2)通过new在本类中创建一个本类对象* (3)定义一个公有的方法,将在该类中所创建的对象返回* 优点:从它的实现中我们可以看到,这种方式的实现比较简单,在类加载的时候就完成了实例化,避免了线程的同步问题。* 缺点:由于在类加载的时候就实例化了,所以没有达到Lazy Loading(懒加载)的效果,也就是说可能我没有用到这个实例,但是它* 也会加载,会造成内存的浪费(但是这个浪费可以忽略,所以这种方式也是推荐使用的)。* <pre>*/
public class SingletonEHan {private static final SingletonEHan instance = new SingletonEHan();private SingletonEHan() {}private static SingletonEHan getInstance() {return instance;}
}

(2) 懒汉式--(线程安全,可用,效率稍低)

/*** 单例模式--懒汉式线程安全的:(线程安全,效率低,不推荐使用)* <pre>* 缺点:效率太低了,每个线程在想获得类的实例时候,执行getInstance()方法都要进行同步。* 而其实这个方法只执行一次实例化代码就够了,* 后面的想获得该类实例,直接return就行了。方法进行同步效率太低要改进。* </pre>*/
public class SingletonLanHan {private static SingletonLanHan instance = null;private SingletonLanHan() {}public static synchronized SingletonLanHan getInstance() {if (instance == null) {instance = new SingletonLanHan();}return instance;}
}

(3) 懒汉式--双重校验锁(线程安全, 推荐)

/*** 单例模式--单例模式懒汉式双重校验锁(线程安全, 推荐)* <pre>* 懒汉式变种,属于懒汉式的最好写法,保证了:延迟加载和线程安全* </pre>*/
public class SingletonDoubleCheck {private static volatile SingletonDoubleCheck instance = null;   //关键点0:声明单例对象是静态的private SingletonDoubleCheck() {                            //关键点1:构造函数是私有的}public static SingletonDoubleCheck getInstance() {          //通过静态方法来构造对象if (instance == null) {                                 //关键点2:判断单例对象是否已经被构造synchronized (SingletonDoubleCheck.class) {         //关键点3:加线程锁if (instance == null) {                         //关键点4:二次判断单例是否已经被构造instance = new SingletonDoubleCheck();}}}return instance;}
}
注:instance加了volatile关键字来修饰,既然synchronized已经起到了多线程下原子性、有序性、可见性的作用,为什么还要加 volatile呢,主要原因如下:
a. 防止指令重排序
具体可见: 单例模式与双重检测 - 设计模式 - Java - ITeye论坛

疑问:为什么instance要加volatile关键字来修饰?

解答:

instance = new SingletonDoubleCheck();分三步执行

①给 instance 分配内存

②调用 Singleton 的构造函数来初始化成员变量

③将instance对象指向分配的内存空间(执行完这步 instance 就为非 null 了)。

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

由于 JVM 具有指令重排的特性,有可能执行顺序变为了 >③>②,具体如下:

public class SingletonDoubleCheck {private static SingletonDoubleCheck instance = null;private SingletonDoubleCheck() {}public static SingletonDoubleCheck getInstance() {if (instance == null) {    // B线程检测到instance不为空synchronized (SingletonDoubleCheck.class) {if (instance == null) {instance = new SingletonDoubleCheck();    // A线程被指令重排了,刚好先赋值了;但还没执行完构造函数。}}}return instance;    // 后面B线程执行时将引发:对象尚未初始化错误。}
}

使用 volatile 的主要原因是其另一个特性:禁止指令重排序优化。也就是说,在 volatile 变量的赋值操作后面会有一个内存屏障(生成的汇编代码上),读操作不会被重排序到内存屏障之前。比如上面的例子,取操作必须在执行完 ①-②-③ 之后或者 ①-③-② 之后,不存在执行到 ①-③ 然后取到值的情况。

(4) 静态内部类--(线程安全,推荐)

/*** 单例模式--内部类(线程安全,推荐)* <pre>* 这种方式跟饿汉式方式采用的机制类似,但又有不同。* 两者都是采用了类装载的机制来保证初始化实例时只有一个线程。* 不同的地方:* 在饿汉式方式是只要Singleton类被装载就会实例化,* 内部类是在需要实例化时,调用getInstance方法,才会装载SingletonHolder类* 优点:避免了线程不安全,延迟加载,效率高。* <pre>*/
public class SingletonLazy {private SingletonLazy() {}private static class SingletonHolder {private static final SingletonLazy INSTANCE = new SingletonLazy();}public static SingletonLazy getInstance() {return SingletonHolder.INSTANCE;}
}

(5) 枚举--(线程安全,推荐)

/*** 单例模式--枚举(线程安全,可用)* <pre>* 这里SingletonEnum.instance* 这里的instance即为SingletonEnum类型的引用所以得到它就可以调用枚举中的方法了。* 借助JDK1.5中添加的枚举来实现单例模式。不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象* </pre>*/
public enum SingletonEnum {INSTANCE;public static void main(String[] args) {SingletonEnum obj = SingletonEnum.INSTANCE;System.out.println(obj);}
}

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

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

相关文章

一起学习python——基础篇(15)

今天讲一下python的网络请求方法。 如果使用python语言做接口测试&#xff0c;可以安装Requests模块。 安装步骤&#xff1a;打开编程软件&#xff0c;然后点击settings——>Project:System——>Python Interpreter——>加号——>搜索栏里面输入“Requests”——&…

Redis中的集群(一)

集群 概述 Redis集群是Redis提供的分布式数据库方案&#xff0c;集群通过分片(sharding)来进行数据共享&#xff0c;并提供复制和故障转移功能 节点 一个Redis集群通常由多个节点(node)组成&#xff0c;在刚开始的时候&#xff0c;每个节点都是相互独立的&#xff0c;它们都…

appium

app元素抓取在线工具 Appium Inspector by Appium Pro appium安装&#xff08;通过node.js安装&#xff09; Python3Appium安装使用教程_python_脚本之家 Node version is 18.17.1

[计算机网络] 当输入网址到网页

HTTP 首先&#xff0c;对URL进行解析&#xff0c;URL包含了Web服务器和对应的文件&#xff08;文件路径&#xff09; URL是请求服务器中的文件资源 通过Web服务器和对应文件来生产HTTP包&#xff08;超文本传输协议&#xff09; DNS 根据域名查询对应的IP地址 域名的层级 根…

代理模式:控制对象访问的智能方式

在面向对象的软件开发中&#xff0c;代理模式是一种结构型设计模式&#xff0c;它为其他对象提供一个代理或占位符以控制对这个对象的访问。代理模式在实现权限控制、延迟初始化和远程对象访问等方面非常有用。本文将详细介绍代理模式的定义、实现、应用场景以及优缺点&#xf…

奎芯科技:智能时代的芯片上游企业如何突破?

半导体IP&#xff08;Intellectual Property&#xff0c;知识产权&#xff09;&#xff0c;通常也称作IP核&#xff08;IP core&#xff09;&#xff0c;指芯片设计中预先设计、验证好的功能模块&#xff0c;主要服务于芯片设计&#xff0c;因部分通用功能模块在芯片中被反复使…

鸿蒙南向开发:【智能烟感】

样例简介 智能烟感系统通过实时监测环境中烟雾浓度&#xff0c;当烟雾浓度超标时&#xff0c;及时向用户发出警报。在连接网络后&#xff0c;配合数字管家应用&#xff0c;用户可以远程配置智能烟感系统的报警阈值&#xff0c;远程接收智能烟感系统报警信息。实现对危险及时报…

智慧港口多场景解决方案(二)

港机智能化管理 港机智能化管理解决方案 [功能特点」 专门定制适合港机应用的专业摄像机高性能无线传输系统,高带宽、高抗干扰能力、高频谱利用率高移动性:支持港机移动高清视频的实时传送多业务承载能力:支持视频图像、CMS、告警数据、生产数据等业务的同时接入统一管理平台…

微软卡内基梅隆大学:无外部干预,GPT4等大语言模型难以自主探索

目录 引言&#xff1a;LLMs在强化学习中的探索能力探究 研究背景&#xff1a;LLMs的在情境中学习能力及其重要性 实验设计&#xff1a;多臂老虎机环境中的LLMs探索行为 实验结果概览&#xff1a;LLMs在探索任务中的普遍失败 成功案例分析&#xff1a;Gpt-4在特定配置下的探…

YOLOv8草莓生长状态(灰叶病缺钙需要肥料)检测系统(python开发,带有训练模型,可以重新训练,并有Pyqt5界面可视化)

本次检测系统&#xff0c;不仅可以检测图片、视频或摄像头当中出现的草莓叶子是否有灰叶病&#xff0c;还可以检测出草莓叶是否缺钙、是否需要施肥等状态。基于最新的YOLO-v8训练的草莓生长状态检测模型和完整的python代码以及草莓的训练数据&#xff0c;下载后即可运行&#x…

nvm安装详细教程(安装nvm、node、npm、cnpm、yarn及环境变量配置)

一、安装nvm 1. 下载nvm 点击 网盘下载 进行下载 2、双击下载好的 nvm-1.1.12-setup.zip 文件 3.双击 nvm-setup.exe 开始安装 4. 选择我接受&#xff0c;然后点击next 5.选择nvm安装路径&#xff0c;路径名称不要有空格&#xff0c;然后点击next 6.node.js安装路径&#…

【氮化镓】缓冲层结构对GaN HEMT射频性能的影响

【Effect of different layer structures on the RF performance of GaN HEMT devices】 研究总结&#xff1a; 本研究探讨了不同缓冲层结构对氮化镓高电子迁移率晶体管&#xff08;GaN HEMT&#xff09;射频性能的影响。通过对比三种不同缓冲层结构的GaN HEMT设备&#xff0…

【YOLOv9改进[损失函数]】使用MPDIou回归损失函数帮助YOLOv9模型更优秀

本文中&#xff0c;第一部分概述了各种回归损失函数&#xff0c;当然也包括了今天的主角MPDIou。第二部分内容为在YOLOv9中使用MPDIou回归损失函数的方法。 1 回归损失函数&#xff08;Bounding Box Regression Loss&#xff09; 边界框回归损失计算的方法包括GIoU、DIoU、CI…

伦敦银实时行情看不懂?因为你不懂这些

伦敦银每天的实时行情都在变化&#xff0c;市场走势会在混沌和明朗之间反复切换&#xff0c;如果你是一名趋势交易者&#xff0c;明朗的市况才是创造利润的时候&#xff0c;所以在如市之前&#xff0c;你就要去思考&#xff0c;自己该如何度过那些持续时间可能很长的震荡行情。…

保姆级python项目离线部署服务器教程只需这一篇就够了(建议收藏)

保姆级python项目离线部署服务器教程只需这一篇就够了 这篇文章主要记录我在湖南长沙国网电科院一次python项目部署,由于我主要是做Java后端开发对python确实不太了解因此记录下这次教程 环境介绍 ​ 服务器为linux的centos系统具体7还是8我不太清楚,全程为没有网络环境,所以环…

(Java)数据结构——图(第七节)Folyd实现多源最短路径

前言 本博客是博主用于复习数据结构以及算法的博客&#xff0c;如果疏忽出现错误&#xff0c;还望各位指正。 Folyd实现原理 中心点的概念 感觉像是充当一个桥梁的作用 还是这个图 我们常在一些讲解视频中看到&#xff0c;就比如dist&#xff08;-1&#xff09;&#xff0…

volta(轻松切换管理Node.js版本)

Node.js版本管理 Volta提供了一个简单直观的命令行界面&#xff0c;可以轻松地安装、卸载、更新和切换Node.js版本。 Volta 既可以全局使用&#xff0c;也可以在项目级别使用&#xff0c;可以为每个项目单独设置node版本&#xff0c;nvm不行。 下载安装Volta 参考&#xff1a; …

为什么要“挺”鸿蒙?

鸿蒙到底是什么&#xff1f; 随着5G、物联网等技术的快速发展&#xff0c;智能终端设备的应用场景也越来越广泛。为了满足不同设备间的互联互通需求&#xff0c;华为在2019年推出了自主研发的操作系统——鸿蒙OS。值得关注的是&#xff0c;这也是首款国产操作系统。 要了解鸿…

【可视化大屏开发】18. 加餐-ECharts+百度地图API实现热力图

ECharts结合百度地图API能获得更好的使用体验。 效果展示 放大后的效果 切换卫星地图模式 实现步骤 1. 通过Python实现GPS数据模拟 2. 通过IDEA开发地图 通过Python实现GPS数据模拟 import random from math import cos, sin, radians, sqrt import jsondef generate_random…

嵌入式|蓝桥杯STM32G431(HAL库开发)——CT117E学习笔记13:RTC实时时钟

系列文章目录 嵌入式|蓝桥杯STM32G431&#xff08;HAL库开发&#xff09;——CT117E学习笔记01&#xff1a;赛事介绍与硬件平台 嵌入式|蓝桥杯STM32G431&#xff08;HAL库开发&#xff09;——CT117E学习笔记02&#xff1a;开发环境安装 嵌入式|蓝桥杯STM32G431&#xff08;…