设计模式--单例模式【创建型模式】

设计模式的分类

我们都知道有 23 种设计模式,这 23 种设计模式可分为如下三类:

  • 创建型模式(5 种):单例模式、工厂方法模式、抽象工厂模式、建造者模式、原型模式。
  • 结构型模式(7 种):适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。
  • 行为型模式(11 种):策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。

在这里插入图片描述

设计模式系列文章传送门

设计模式的 7 大原则

什么是单例模式

单例模式是指在内存中只会创建且仅创建一次对象的设计模式,在程序中多次使用同一个对象时,为了防止频繁地创建对象使得 JVM 内存飙升,单例模式可以让程序仅在内存中创建一个对象,让所有需要调用的地方都共享这一单例对象,这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建,这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。

单例模式的分类

单例模式有两种类型,如下:

  • 饿汉式:类加载时候就会创建该类的单例对象。
  • 懒汉式:类加载时候并不会创建单例对象,只有首次调用该对象时才会创建。

静态变量实现单例模式

静态变量实现单例模式是一种饿汉式的实现,代码如下:

public class StaticSingleton {//私有构造方法private StaticSingleton() {}//创建静态单例对象(是一个成员变量)private static final StaticSingleton STATIC_SINGLETON= new StaticSingleton();//对外提供静态方法获取静态单例对象public static StaticSingleton getInstance() {return STATIC_SINGLETON;}}

静态变量单例模式的关键是类的构造方法私有,同时创建一个静态私有的实例对象,通过静态方法来让调用者来获取该单例对象,同时该类加载的时候就会创建该单例对象。

静态代码块实现单例模式

静态代码块实现单例模式和静态变量实现单例模式基本相同,也是一种饿汉式的实现,代码如下:

public class StaticBlockSingleton {//私有构造方法private StaticBlockSingleton() {}//创建静态单例对象(是一个成员变量)private static final StaticBlockSingleton STATIC_SINGLETON;//静态代码块创建实例对象static {STATIC_SINGLETON = new StaticBlockSingleton();}//对外提供静态方法获取静态单例对象public static StaticBlockSingleton getInstance() {return STATIC_SINGLETON;}}

懒汉单例模式–线程不安全

线程不安全的懒汉单例模式实现代码如下:

public class NotSafeSingleton {//私有构造方法private NotSafeSingleton() {}//定义一个静态私有的成员变量private static NotSafeSingleton staticSingleton;//对外提供静态方法获取静态单例对象 没有提前创建对象 因此是懒汉模式public static NotSafeSingleton getInstance() {if (staticSingleton == null) {staticSingleton = new NotSafeSingleton();}return staticSingleton;}}

为什么说上面这种写法不安全呢?

乍一看确实没有什么问题,我们提供了一个静态方法供调用者获取单例对象,在静态方法中我们先进行了对象为 null 判断,似乎没有什么问题,在没有并发的情况下确实没有问题,但是如果有并发呢?两个调用者线程同时进入了 staticSingleton == null 判断,此时谁也没有执行对象的创建,因此都可以继续往下执行 new NotSafeSingleton() 这句代码,至此这种单例模式为什么不是线程安全的已经很明了了,我们在项目开发中也要考虑这种并发的场景。

懒汉单例模式–线程安全

上面分享了线程不安全的懒汉模式实现,现在我们分享一下线程安全的懒汉模式,这里使用 synchronized 来保证线程安全,代码如下:

public class SafeSingleton {//私有构造方法private SafeSingleton() {}//定义一个静态私有的成员变量private static SafeSingleton staticSingleton;//对外提供静态方法获取静态单例对象 没有提前创建对象 因此是懒汉模式 使用 synchronized 进行加锁public static synchronized SafeSingleton getInstance() {if (staticSingleton == null) {staticSingleton = new SafeSingleton();}return staticSingleton;}}

使用了 synchronized 之后就不会出现两个线程同时执行 staticSingleton == null 的判断了,因此也就没有了线程安全问题。

懒汉单例模式–双重检查(Double Check)

上面分享了使用 synchronized 来实现线程安全的懒汉单例模式,现在我们分享一种更优雅的线程安全的懒汉模式,代码如下:

public class DoubleCheckSingleton {//私有构造方法private DoubleCheckSingleton() {}//定义一个静态私有的成员变量private static DoubleCheckSingleton staticSingleton;//对外提供静态方法获取静态单例对象 没有提前创建对象 因此是懒汉模式 使用 synchronized 进行加锁public static DoubleCheckSingleton getInstance() {//第一次为空判断if (staticSingleton == null) {//加锁synchronized (DoubleCheckSingleton.class){//第二次为空判断if(staticSingleton == null){staticSingleton = new DoubleCheckSingleton();}}}return staticSingleton;}}

对比上面直接在方法上使用了 synchronized,这里调整了 synchronized 的位置,让锁的范围变的更小,同时也提升了效率,因此懒汉单例模式真正发生线程不安全的地方就是创建对象的那行代码,而创建的操作只需要进行一次就可以了,单量的请求都不会走到创建对象的这行代码,因此我们没必要把 synchronized 加在方法上,因此就有了双重检查的懒汉单例模式,为什么需要双重检查?这种双重检查懒汉单例模式真的就是完美的吗?

为什么需要双重检查?

  1. 假设有线程 1 和现成 2 都来调用 getInstance 方法获取单例对象,进入第一个 staticSingleton == null 的判断时候,线程 1 和线程 2 都满足条件,都可以继续往下执行。
  2. 接着线程 1 获取到锁,再次进行 staticSingleton == null 的判断,此时 staticSingleton 任然为 null,线程 1 就会开始创建对象,线程 2 等待获取锁。
  3. 线程 1 创建完对象后释放锁,线程 2 获取到锁后来执行 staticSingleton == null 判断,因为线程 1 已经创建了对象,staticSingleton 不会 null,线程 2 就不会再次创建对象了。

至此我们就知道双重检查的必要性了。

双重检查懒汉单例模式真的就是完美的吗?

双重检查的饿汉单例模式看起来是很完美,实际上至少在 99.99% 的场景也不会出问题,但是它并不 100 完美,我们来回忆一下对象创建的过程,如下:

  1. 在 JVM 堆上开辟地址空间。
  2. 把开辟的地址空间赋值给 Java 虚拟机栈上的变量(到这里对象以及不为 null 了,因此完成了对象实例化)。
  3. 初始化对象,完成属性赋值。

总结来说就是对象先实例化后初始化,对象实例化之后因为在 JVM 堆上开辟了内存空间,因此就不会 null 了。

双重检查的饿汉单例模式的问题就出现在对象的实例化和初始化上,本质上对象的实例化和初始化没有什么问题,但是 JVM 又有一个指令重排的操作,具体来说就是对象分配了地址空间之后初始化之前,把对象的地址赋值给了静态成员变量 staticSingleton,而恰巧此时其他线程调用了 getInstance 方法,执行第一个 staticSingleton == null 判断时候就发现 staticSingleton 不为 null,就直接返回 staticSingleton 给调用者使用,结果可想而知,会发生空指针异常,那如何来解决这个问题呢?

volatile 关键字

一个变量如果被 volatile 关键字修饰后,就会具备一下两项能力:

  • 保证线程间的可见性:当一个线程对共享变量进行了修改,其他线程可以立即看到修改后的最新值,volatile 能让行缓存无效,因此能读到内存中最新的值。
  • 禁止进行指令重排序:用 volatile 修饰共享变量会在读、写共享变量时加入不同的屏障,阻止其他读写操作越过屏障,从而达到阻止重排序的效果。

上面我们谈到因为指令重排导致了 staticSingleton 对象已经不会 null 了,从而导致了调用者最终的空指针异常,我们了解了 volatile 关键字的作用后,对双重检查饿汉单例模式进行升级,代码如下:

public class DoubleCheckSingleton {//私有构造方法private DoubleCheckSingleton() {}//定义一个静态私有的成员变量private static volatile DoubleCheckSingleton staticSingleton;//对外提供静态方法获取静态单例对象 没有提前创建对象 因此是懒汉模式 使用 synchronized 进行加锁public static DoubleCheckSingleton getInstance() {//第一次为空判断if (staticSingleton == null) {//加锁synchronized (DoubleCheckSingleton.class) {//第二次为空判断if (staticSingleton == null) {staticSingleton = new DoubleCheckSingleton();}}}return staticSingleton;}}

饿汉单例模式–静态内部类

前面我们分享的静态相关的单例模式都是懒汉模式,而静态内部类的单例模式是饿汉的,代码如下:

public class StaticInnerClassSingleton {//私有构造方法private StaticInnerClassSingleton() {}//对外提供静态方法获取静态单例对象 没有提前创建对象 因此是懒汉模式 静态方法中调用静态内部内的静成员变量public static StaticInnerClassSingleton getInstance() {return SingletonHolder.STATIC_SINGLETON;}//静态内部类private static class SingletonHolder {//在内部类中创建一个 单例对象private static  StaticInnerClassSingleton STATIC_SINGLETON = new StaticInnerClassSingleton();}}

可以看到静态内部类方式实现单例模式,最终对象的创建是由静态内部类创建的,因为 JVM 在加载外部类的过程中并不会去加载静态内部类,只有当静态内部类被的属性、方法被调用时候,才会进行初始化,可以看到我们在使用静态内部类实现恶汉单例模式并没有加锁,而且也是线程安全的,这表名静态内部类实现的单例模式是一种不错的选择。

饿汉单例模式–枚举

使用枚举类实现单例模式其实是最单例实现模式实现,因为枚举类型是线程安全的,并且只会装载一次,使用枚举实现单例模式充分的利用了枚举的特性,枚举的写法非常简单,代码如下:

public enum EumSingleton {INSTANCE;
}

枚举实现单量模式不仅简单,而且使用枚举实现单例模式是所用单例模式实现中唯一不会被破坏的单例实现模式(反射可以破坏单例模式)。

总结:本篇简单分享了单例模式的几种实现方式,并对各种单例模式的实现方式进行了剖析,希望可以帮助到有需要的朋友。

如有不正确的地方欢迎各位指出纠正。

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

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

相关文章

neo4j 图表数据导入到 TuGraph

neo4j 图表数据导入到 TuGraph 代码文件说明后文 前言:近期在引入阿里的 TuGraph 图数据库,需要将 原 neo4j 数据导入到新的 tugraph 数据库中。预期走csv文件导入导出,但因为格式和数据库设计问题,操作起来比较麻烦(可能是个人没…

模具生产过程中的标签使用流程图

①NFC芯片嵌入周转筐,通过读卡器读取CK_Label_v3的数据,并将这些信息上传至服务器进行存储; ②服务器随后与客户的WMS(仓库管理系统)进行交互,记录和同步注塑机的原始数据; ③当周转筐内的模具…

Linux线程同步

1 线程同步概念 假设有有三个线程A,B,C,当前一个线程A对内存中的共享资源进行访问时,其它线程B,C都不可以对这块内存进行操作,直到线程A对这块内存访问完毕为止,B,C中的一个才能访问这块内存,剩…

Vue与React:前端框架的巅峰对决

文章目录 一、引言(一)前端框架发展现状简述 二、Vue 与 React 框架概述(一)Vue.js 简介(二)React.js 简介 三、开发效率对比(一)Vue 开发效率分析(二)React …

项目管理工具Maven(一)

Maven的概念 什么是Maven 翻译为“专家”,“内行”Maven是跨平台的项目管理工具。主要服务于基于Java平台的项目构建,依赖管理和项目信息管理。什么是理想的项目构建? 高度自动化,跨平台,可重用的组件,标准…

【Prometheus 】【实战篇(五)】深入解析 Prometheus 监控指标类型:Counter、Gauge、Histogram 和 Summary

Prometheus 提供了四种核心的指标类型,分别是 Counter(计数器)、Gauge(仪表)、Histogram(直方图)和 Summary(摘要)。这些指标类型在客户端库中有具体的使用说明&#xff…

outlook smtp 发送邮件

前提条件 开通 app password 开通 smtp 服务 import smtplib from email.mime.multipart import MIMEMultipart from email.mime.text import MIMETextdef send_html_email_smtp(sender_email, sender_password, recipient_email, subject, html_content):# Create the messag…

如何利用Python爬虫获得Lazada商品评论列表

在电商领域,用户评论是了解商品口碑和市场反馈的重要渠道。对于Lazada这样的东南亚电商平台,获取商品评论列表对于市场分析、产品改进和销售策略的制定至关重要。本文将详细介绍如何使用Python编写爬虫程序,以获取Lazada商品的评论列表。 一、…

穷举vs暴搜vs深搜vs回溯vs剪枝系列一>找出所有子集的异或总和再求和

题目&#xff1a; 解析&#xff1a; 代码&#xff1a; private int ret;//返回周结果private int path;//枚举一个元素就异或进去public int subsetXORSum(int[] nums) {dfs(nums, 0);return ret;} private void dfs(int[] nums, int pos){ret path;for(int i pos; i <…

如何设计一个秒杀系统

开局一张图 结局要说清 对于设计一个秒杀系统&#xff0c;结合图片分层结构&#xff0c;根据每一层从访问层&#xff0c;负载层&#xff0c;服务层&#xff0c;业务层&#xff0c;支撑层&#xff0c;数据层&#xff0c;详细说明每一层应该怎么设计。 应该注意那些事项。比如访…

【LeetCode】45.跳跃游戏II

题目链接&#xff1a; 45.跳跃游戏 题目描述&#xff1a; 思路一&#xff08;广度优先搜索算法BFS&#xff09; 通过广度优先搜索算法寻找最短距离 代码实现&#xff1a; class Solution { public:int jump(vector<int>& nums) {int n nums.size();if(n<1) re…

WPF ControlTemplate 控件模板

区别于 DataTemplate 数据模板&#xff0c;ControlTemplate 是控件模板&#xff0c;是为自定义控件的 Template 属性服务的&#xff0c;Template 属性类型就是 ControlTemplate。 演示&#xff0c; 自定义一个控件 MyControl&#xff0c;包含一个字符串类型的依赖属性。 pub…

clickhouse-题库

1、clickhouse介绍以及架构 clickhouse一个分布式列式存储数据库&#xff0c;主要用于在线分析查询 2、列式存储和行式存储有什么区别&#xff1f; 行式存储&#xff1a; 1&#xff09;、数据是按行存储的 2&#xff09;、没有建立索引的查询消耗很大的IO 3&#xff09;、建…

arcgisPro将面要素转成CAD多段线

1、说明&#xff1a;正常使用【导出为CAD】工具&#xff0c;则导出的是CAD三维多线段&#xff0c;无法进行编辑操作、读取面积等。这是因为要素面中包含Z值&#xff0c;导出则为三维多线段数据。需要利用【复制要素】工具禁用M值和Z值&#xff0c;再导出为CAD&#xff0c;则得到…

【Unity3D】实现可视化链式结构数据(节点数据)

关键词&#xff1a;UnityEditor、可视化节点编辑、Unity编辑器自定义窗口工具 使用Newtonsoft.Json、UnityEditor相关接口实现 主要代码&#xff1a; Handles.DrawBezier(起点&#xff0c;终点&#xff0c;起点切线向量&#xff0c;终点切线向量&#xff0c;颜色&#xff0c;n…

详细解读TISAX认证的意义

详细解读TISAX认证的意义&#xff0c;犹如揭开信息安全领域的一颗璀璨明珠&#xff0c;它不仅代表了企业在信息安全管理方面的卓越成就&#xff0c;更是通往全球汽车供应链信任桥梁的关键一环。TISAX&#xff0c;即“Trusted Information Security Assessment Exchange”&#…

Ubuntu搭建ES8集群+加密通讯+https访问

目录 写在前面 一、前期准备 1. 创建用户和用户组 2. 修改limits.conf文件 3. 关闭操作系统swap功能 4. 调整mmap上限 二、安装ES 1.下载ES 2.配置集群间安全访问证书密钥 3.配置elasticsearch.yml 4.修改jvm.options 5.启动ES服务 6.修改密码 7.启用外部ht…

JS设计模式之中介者模式

前言 中介者模式&#xff08;Mediator Pattern&#xff09;是一种行为设计模式&#xff0c;旨在通过引入一个中介者对象来减少多个对象之间的直接交互&#xff0c;降低对象之间的耦合度。 这种模式使得对象之间的通信通过中介者来协调&#xff0c;从而减少了对象之间的直接依…

Ubuntu下C语言操作kafka示例

目录 安装kafka&#xff1a; 安装librdkafka consumer Producer 测试运行 安装kafka&#xff1a; Ubuntu下Kafka安装及使用_ubuntu安装kafka-CSDN博客 安装librdkafka github地址&#xff1a;GitHub - confluentinc/librdkafka: The Apache Kafka C/C library $ apt in…

在 Unity 6 中使用APV为您的世界创建全局照明的新方法(一)

Unity 6 中推出的新照明功能让您能够更快速、更高效的完成对烘焙场景的照明工作&#xff0c;在本文中我们将与大家详细分享在 Unity 6 中应用自适应探针卷创建快速全局光照的更多细节与具体应用方法。由于内容比较丰富&#xff0c;我们将把内容分为三篇文章&#xff0c;以便大家…