【JavaEE】单例模式如何保证在多线程环境下线程安全高可用?

news2024/12/23 11:54:56

文章目录

  • 1 单例模式回顾
  • 2 饿汉式单例模式的实现
  • 3 懒汉式单例模式的实现
  • 4 单例模式的线程安全问题分析
  • 5 线程安全的懒汉式实现
  • 6 总结


1 单例模式回顾

 单例模式是设计模式的一种。而设计模式就是针对我们实际开发中写代码所遇到的不同场景所设立的解决方案。在笔者JavaSE阶段的文章中,我们曾经聊到过单例模式,这里给出文章链接,有兴趣的读者可以自行阅读:单例模式懒汉式与饿汉式的实现 ,在本文中,笔者也会进行必要的总结。下面,进入正文:
在这里插入图片描述

 所谓类的单例设计模式,就是采用一定的方法保证在整个软件系统中,对某一个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法。常见的实现方式有两种,懒汉式和饿汉式。

  这里的懒汉和饿汉可以这样理解,对于饿汉来说,肚子很饿,是比较着急去吃饭的,体现出一个着急的态度;而对于懒汉来说,在计算机中,这属于褒义词,指的是“不着急”,只有当你需要的时候,我才给你提供需要的东西,反而更加高效。 所以说:

  1. 懒汉式的单例模式,是比较着急去创建对象实例的;
  2. 饿汉式的单例模式,不太着急去创建对象实例,只有在需要的时候,才去创建。

 现在,饿汉式和懒汉式的区别我们了解清楚了,无非就是创建对象的时机不同。 但是,无论是哪种实现方式,都需要满足一个大前提:对于该类,只能存在一个对象实例。

 因此,为了做到这点,我们可以考虑将构造器私有化,这样在外部就没有办法手动创建对象。同时,在类中,我们需要手动创建一个对象,并对外提供一个公有的方法,使得该创建的对象,能够被外部所获取。

 下面我们来具体聊一聊,饿汉式与懒汉式该如何实现单例模式。
在这里插入图片描述


2 饿汉式单例模式的实现

对于饿汉式的实现,主要步骤如下:(饿汉主要体现在直接创建对象)

  1. 先将构造器私有化, 避免用户直接在外部 new 新对象。
  2. 在类的内部属性部分直接创建一个对象(我们已经将构造器私有化了,因此在外部没有办法 new 新对象,所以只能在类的内部创建对象)。
  3. 向外提供一个公共的静态方法,用于外部引用该对象。(创建好了对象后,如果对外不提供公共方法,我们就没有办法使用这个被创建的对象。)

这里有一点值得说明,静态?为什么需要使用static去修饰对象和方法呢?

在类的外部,我们无法使用 new 新建对象,也就是说,我们只能通过 类名.xxx 的方式来返回创建的对象,因此,获取对象的对外公共方法需要使用 static 关键字修饰。而该方法中,需要返回类中创建的对象,静态方法只能使用静态变量,因此,对象属性也应当设置为 static。

实现代码如下:

/**
 * @author 兴趣使然黄小黄
 * @version 1.0
 * 单例模式的饿汉式实现
 */
@SuppressWarnings({"all"})
public class Singleton {

    // 提供一个对象
    private static Singleton instance = new Singleton();

    // 防止外部new对象
    private Singleton(){}

    // 使得外部能够访问到创建的对象
    public static Singleton getInstance(){
        return instance;
    }
}


3 懒汉式单例模式的实现

对于懒汉式的实现,主要步骤如下:(懒汉主要体现在不直接创建对象,而是在需要的时候再创建)

  1. 先将构造器私有化, 避免用户直接在外部 new 新对象。
  2. 在类的内部 声明一个对象引用,但是不创建, 即先不 new;
  3. 向外提供一个公共的静态方法,用于创建对象并返回对象引用。 需要在该方法中创建对象,并返回给外部。

实现代码如下:

/**
 * @author 兴趣使然黄小黄
 * @version 1.0
 * 单例模式的懒汉式实现
 */
@SuppressWarnings({"all"})
public class Singleton2 {

    // 提供一个对象的引用
    private static Singleton2 instance = null;

    // 防止外部new对象
    private Singleton2(){}

    // 用于创建对象或使得外部能够访问到创建的对象
    public static Singleton2 getInstance(){
        if (instance == null){
            instance = new Singleton2();
        }
        return instance;
    }
}

4 单例模式的线程安全问题分析

以上,我们成功实现了饿汉式与懒汉式的单例模式。在串行、单线程环境中,饿汉式和懒汉式实现的单例模式都没有什么问题。那么,如果是在多线程环境呢?这样就存在了一些线程安全问题。

什么是线程安全问题?

线程安不安全,指的是在多线程环境中,代码是否会产生bug。如果,在多线程环境中,不会产生bug,那么我们就说,这是线程安全的;如果,在多线程环境中,会产生bug,我们就说这样的实现方式是不安全的。

什么样的情况下会导致线程安全问题呢?

如果多个线程,只是读取同一个变量、资源,而不作修改,此时仍然是线程安全的。如果,存在修改,则可能会出现线程安全问题。

对于饿汉式单例模式的实现,由于其仅仅只是读取了变量的内容,因此,是线程安全的!
在这里插入图片描述

而懒汉式单例模式的实现,就不一样了。在懒汉模式中,既包含了读,又包含了写,且读写还不是原子的(分成了两个步骤~), 所以存在线程安全问题。

比如:两个进程g1,g2同时进入该方法,需要返回给 g1 ,g2 对象实例。当 g1 进入该方法后,进行判断,不为 null,因此创建一个对象,而在创建对象的过程中,g2 刚好也进入了该方法,也被判断成了不为 null,此时,g1, g2就指向不同的对象了,所以它是线程不安全的。

在这里插入图片描述
可能文字叙述,还是有些抽象,下面,对例子进行画图描述,以时间为顺序,同样,进程还是用 g1,g2来表示:
在这里插入图片描述
由于进程调度的时机是不确定的,以上只是列举了一种可能导致线程安全问题的调度方式。

那么如何保证懒汉式单例模式的实现是线程安全的呢?

在这里插入图片描述


5 线程安全的懒汉式实现

如何实现线程安全?
很简单!既然你说读和写分成了两个步骤,会出现线程安全问题,那我 加锁 不就完了!
在这里插入图片描述
嗯,很好,这样就保证了,在进行读和写操作的时候,只有一个线程在操作,就可以避免另一个线程同时进入 if 语句去创建对象了!

可是,这样真的好吗?

我们简单分析一下,对于懒汉式的实现,是何时会导致线程不安全?

对于懒汉式的代码来说,线程不安全是发生在 instance 被初始化之前!!!在未初始化的时候,即 instance == null,多个线程去调用 getInstance 可能就同时涉及到读和改的问题,引发线程安全问题。
但是,一旦 instance 不是null,已经被初始化后,getInstance方法就只剩下读操作了,此时一定是线程安全的。

那么,我们刚刚的加锁方式,就有了些问题:无论是 instance 初始化之前,还是 instance 初始化之后,都会导致阻塞,存在大量的锁竞争!

而我们知道,对于加锁操作来说,加锁确实可以让代码保证线程安全,但是也会付出相应的代价, 最典型的代价就是程序运行变慢了。本来我多个线程运行的好好的,你给我加了把锁,其他线程不就进不去了吗?(一定程度上的阻塞)

因此,我们需要做出如下改进:再加一个判断!判断 instance 是否被初始化,如果已经初始化,就不必进入加锁的代码块了!
在这里插入图片描述
但是!这样还会有问题,即 内存可见性 问题。

如果多个线程,都去调用 getInstance方法,就会造成大量的读 instance 操作。而,我们知道,对于在内存读和在寄存器读来说,后者的效率更高。因此,编译器会自作主张,对代码进行优化:将读内存操作优化成读寄存器操作。
也就是说,即使某个线程将instance初始化了,但是由于编译器的优化,在其他线程的视角看起来,instance 还是null。

也就是说,这里的内存可见性问题,会导致第一个if语句失效,即不该加锁的时候还会加锁。因此,我们需要保证内存可见!即使用 volatile 对 instance 进行修饰。
在这里插入图片描述

完整代码如下:

/**
 * @author 兴趣使然黄小黄
 * @version 1.0
 * 单例模式的懒汉式实现
 * 保证线程安全
 */
@SuppressWarnings({"all"})
public class Singleton2 {

    // 提供一个对象的引用
    private static volatile Singleton2 instance = null;

    // 防止外部new对象
    private Singleton2(){}

    // 用于创建对象或使得外部能够访问到创建的对象
    public static Singleton2 getInstance(){
        if (instance == null){
            synchronized(Singleton2.class){
                if (instance == null){
                    instance = new Singleton2();
                }
            }
        }
        return instance;
    }
}

6 总结

 对于饿汉式单例模式,由于只存在对同一变量的读操作,不会导致线程安全问题。

对于懒汉式单例模式,实现线程安全主要有如下三步:

  1. 在正确的位置加锁!
  2. 双重 if 判定!
  3. volatile!

 以上便是本文的全部内容啦!创作不易,如果你有任何问题,欢迎私信,感谢您的支持!

在这里插入图片描述

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

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

相关文章

Vue组件化编程需要注意的命名规则

在Vue组件化编程过程中,开始接触的不太注意命名规则,比如对于组件的内部参数命名以及在父组件中使用命名感到模糊,犯一些错误,就感觉在踩坑。 其实,这是对Vue组件化编程中的命名规则没有留意,稍加学习就可以…

java伪随机数生成器

关于随机数的基本概念 1、对随机数性质分类: 随机性:符合该性质的叫弱伪随机数。这种随机数仅可以用于一般应用,无法用在密码学,例如java中的java.util.Random类不可预测性:符合该性质的叫强伪随机数。在密码学中&am…

学习记录660@项目管理一般知识

看了项目管理一般知识这一章的知识,最开始觉得这些内容,觉得太过于书面化,比如关于什么是项目管理,都要用一段正式定义,充满了国内教育的繁琐感,但是细细品味觉得这些定义是很有道理的,并不是多…

保障接口数据安全的十种方案

视频介绍 数据加密 --主要针对网络抓包 AES 对称加密 RES 非对称加密 实践中直接使用 HTTPS 对于用户个人信息及密码等敏感信息 可额外进行加密 (如密码会进行md5加密防止撞库) 加签验签 --甄别数据在传输过程中被篡改 通常通过哈希算法 进行验证 需…

【学习笔记】【Pytorch】十、搭建CIFAR-10 model结构和Sequential的使用

【学习笔记】【Pytorch】十、搭建CIFAR-10 model结构和Sequential的使用学习地址主要内容一、CIFAR-10 model结构介绍二、代码实现学习地址 PyTorch深度学习快速入门教程【小土堆】. 主要内容 一、CIFAR-10 model结构介绍 input : 332x32,3通道32x32的图片 -->…

kubernetes学习-快速上手速查手册

目录使用k3s快速搭建k8s安装k8s dashboard使用Helm部署K8S资源k8s核心命令一切推倒重来资源创建方式NamespacePodDeploymentServiceIngressConfigMapJob数据持久化搭建NFSPV和PVC使用k3s快速搭建k8s 官网地址https://www.rancher.cn/k3s/ k3s是一个轻量级的k8s,拥…

【SpringCloud12】Gateway服务网关

1.概述简介 1.1官网 1.Zuul 1.X 2.Gateway 1.2是什么 Cloud全家桶中有个很重要的组件就是网关,在1.x版本中都是采用的Zuul网关;但在2.x版本中,zuul的升级一直跳票,SpringCloud最后自己研发了一个网关替代Zuul,那就…

uboot下识别FAT32格式的U盘报错:## Valid DOS partition found ##

1、出错的现象 (1)U盘被格式成FAT32文件系统,在Windows和Linux系统中都可以正常识别并挂载,在uboot下可以正常识别但是不能挂载; (2)在uboot下使用usb命令可以探测到U盘,但是用fatls、fatinfo等命令去挂载U盘时会失败,…

Spring MVC+Spring+Mybatis实现支付宝支付功能

本教程详细介绍了如何使用ssm框架实现支付宝支付功能。本文章分为两大部分,分别是「支付宝测试环境代码测试」和「将支付宝支付整合到ssm框架」,详细的代码和图文解释,自己实践的时候一定仔细阅读相关文档,话不多说我们开始。 本…

永磁同步电机(PMSM)磁场定向控制(FOC)转速环PI调节器参数整定

文章目录前言一、调节器的工程设计方法二、转速环PI调节器的参数整定2.1.转速环的结构框图2.2.典型II型系统2.3.转速环PI参数整定计算公式三、转速环PI调节器设计实例3.1.永磁同步电机磁场定向的转速外环电流内环双闭环控制3.2.转速环PI参数计算3.3.仿真分析总结前言 本章节采…

【2022年度总结2023新年Flag】--2022:高考失利,我奋力奔跑的大一上;2023,朝着成为更优秀的自己迈进ing

🌱博主简介:是瑶瑶子啦,一名大一计科生,目前在努力学习C进阶,JavaSE。热爱写博客~正在努力成为一个厉害的开发程序媛! ✈往期博文回顾:【C语言篇】请把这篇文章推给现在还对指针一知半解的童鞋超生动图解,详…

linux w5500 驱动及使用

1、驱动 驱动来源: a. 内核:linux内核w5500驱动,包含两个源文件w5100.c和w5100-spi.c /kernel/drivers/net/ethernet/wiznet/w5100.c kernel/drivers/net/ethernet/wiznet/w5100-spi.c kernel/drivers/net/ethernet/wiznet/w5100.h 可通过make menuconfi…

Django 入门学习

记录Django入门学习过程 1 基本环境 安装virtualenv pip3.9 install virtualenv 使用命令行创建 创建一个项目 virtualenv dj 进入虚拟环境 cd dj/bin source ./activate 进入成功后bash前面显示括号 安装 Django pip install django3.0.6 安装成功后之星django-…

创建者模式

1.单例模式 单例模式能够保证一个类只有一个实例,并且提供一个可以访问该实例的全局节点 实现步骤 类中添加一个私有静态成员变量用于保存单例实例声明一个公有静态构建方法用于获取单例实例在静态方法中实现"延迟初始化"。 该方法会在首次被调用时创建一…

【云原生kubernetes】k8s中pod使用详解

一、前言 在之前k8s组件一篇中,我们谈到了pod这个组件,了解到pod是k8s中资源管理的最小单位,可以说Pod是整个k8s对外提供服务的最基础的个体,有必要对Pod做深入的学习和探究。 二、再看k8s架构图 为了加深对k8s中pod的理解&#x…

ARM PWM 定时器与实战

一、什么是定时器(timer) 1、定时器是 SoC 中常见外设 (1) 定时器与计数器。计数器是用来计数的(每隔一个固定时间会计一个数);因为计数器的计数时间周期是固定的,因此到了一定时间只要用计数值计数时间周…

contex-m基于IAR工程从boot阶段引导app

目录 1.修改工程 2.修改代码 Boot代码 App代码 3.修改FM33LG04x.icf 4.修改IAR工程icf配置路径 5.修改FM33LG04X.icf链接文件 6.编译工程 7.查看map文件 8.调试程序 1.修改工程 本次调试的demo为《UART0 DMA发送_串口中断示例》,以下修改都是基于该工程&…

初始C语言 - 函数

C语言中函数的分类: 1.库函数:C语言自带的函数,包含大量频繁使用的功能 2.自定义函数:有些库函数没有的,需要程序员自己设计一、库函数通过MSDN、cplusplus.com、cppreference.com这些网站,查看学习函数的作用和用法在使用库函数时…

portswigger靶场中目录遍历

portswigger靶场中目录遍历1.Bp靶场介绍1.1.访问靶场1.2.注意事项2.目录变量漏洞2.1.文件路径遍历2.1.1.开启靶场2.1.2.点击详情2.1.3.抓包2.1.4.修改参数2.1.5.过关2.2.用绝对路径旁路阻止遍历序列2.2.1.开启靶场2.2.2.修改参数2.2.3.过关2.3.非递归地剥离遍历序列2.3.1.开启靶…

Windows下让Qt5 QCamera响应UVC摄像头硬件按钮拍图

QCamera相机类提供了一些基本的功能,包括拍照和录制功能(Windows不支持录制视频),但也有很多接口是没有封装的,比如有些UVC摄像头有物理按键,可以进行拍图等操作,但是QCamera没法响应硬件按钮的…