【Java多线程案例】单例模式

1. 单例模式概念

设计模式:谈到单例模式,我们首先需要知道什么是设计模式,设计模式是软件工程中的一大重要概念,是被广泛认可并使用于解决特定实际问题的代码设计经验,校招中常考的设计模式有单例模式工厂模式 等,而我们需要重点掌握单例模式代码的编写

简单来说,设计模式就是大佬们为了不让我们这些小菜鸟写烂代码而总结出来的代码编写方式

单例模式:单例模式要求类在一个Java进程只能拥有唯一一个实例,而无法创建出多个实例(尝试使用new关键字创建多个实例的时候就会报错)

2. 单例模式代码示例

简单介绍了设计模式以及单例模式的相关概念,光说不练假把式,现在我们就来尝试编写单例模式的代码,在正式编写代码之前,我们需要知道单例模式有两种实现方式:1、饿汉模式;2、懒汉模式,二者之间的差异就在于创建唯一实例的时机不同!

2.1 饿汉模式

2.1.1 代码案例

/**
 * 单例模式类
 */
class Singleton {
    private static Singleton instance = new Singleton(); // 指向唯一实例

    public static Singleton getInstance() {
        return instance;
    }

    private Singleton() {};

}

public class SingletonDemo01 {
    public static void main(String[] args) {
        Singleton instance1 = Singleton.getInstance();
        Singleton instance2 = Singleton.getInstance();
        System.out.println(instance1 == instance2);
    }
}

运行结果如图所示:
image.png

2.1.2 代码分析

  1. 首先观察唯一实例的创建时机private static Singleton instance = new Singleton();该实例在类加载的时候就创建完毕,因此称为"饿汉式"
  2. 再来观察单例模式中的构造方法,普通类的构造方法一般是public修饰的,但是单例模式中将构造方法私有化使用private修饰,因此外界尝试使用new关键字创建实例就会报错
  3. 单例模式提供了一个public static修饰的获取唯一实例的方法,外界只能通过调用这个方法来获取该类的唯一实例,这样就实现了该类只能拥有唯一实例的要求

我相信有的小伙伴一定此时忍不住发出疑问!如果使用反射机制那不就可以创建出多个实例了嘛?事实上如此,但是毕竟反射不属于常规方法,毕竟在代码中滥用反射是非常不好的行为!!!

2.2 懒汉模式

2.2.1 代码案例

/**
 * 懒汉式单例模式
 */
class SingletonLazy {
    private static SingletonLazy instance = null; // 唯一实例

    public static SingletonLazy getInstance() {
        if (instance == null) {
            instance = new SingletonLazy();
        }
        return instance;
    }

    private SingletonLazy() {};

}

2.2.2 代码分析

  1. 懒汉模式的唯一实例并不是在类加载的时候就创建完毕,而是遵循"能不创建就不创建,能晚创建就晚创建"的原则,直到外界有程序调用getInstance方法获取实例时才创建完毕,这就是"懒汉式"的由来
  2. 懒汉模式与饿汉模式一致,都将构造方法设置成私有
  3. 懒汉模式与饿汉模式一致,都提供getInstance方法供外界获取该类的唯一实例

注:在计算机世界中"懒"往往意味着效率高,考虑这样一个场景,有一个10GB的文件,你使用文本编辑器打开,如果采用饿汉式的方式,系统会将10GB的文件一次性加载到内存中,然后统一展示;然而如果使用懒汉式方式打开,则编辑器先加载10KB文件让用户阅读,随着用户进行翻页操作再继续加载10KB文件到内存中。此时懒汉式无疑效率更高

3. 单例模式的线程安全问题

3.1 问题引入

上述关于"单例模式"的介绍只是序幕,毕竟我们本章重点论述的还是多线程主题,下面就有一个重要问题了:上述我们编写的单例模式代码是否存在 线程安全问题 呢?

  • 饿汉模式:由于饿汉模式中创建唯一实例的时机在类加载的时候,而调用getInstance方法执行的过程中直接返回该唯一实例,是纯粹的读取操作,不涉及多个线程修改同一变量的情况,因此天然就是线程安全的!
  • 懒汉模式:想必聪明的小伙伴已经想到了,懒汉模式是不是就是线程不安全的呢?就是这样!下面我们就来考虑多个线程同时执行的情况

演示懒汉模式的线程安全问题
image.png
如图所示:线程t1首先进入条件判断当前实例为null,然后进入if语句块中,但是此时还没有来得及执行new操作创建实例就被调度出CPU,此时线程t2进行判断当前实例为null,然后进入if语句块中创建实例完成返回,此时线程t1重新被CPU调度,接着执行new SingletonLazy()方法,因此创建出了多个实例,即懒汉式单例模式线程不安全!!!

3.2 解决懒汉式线程安全问题

那么如何改进上述代码让"懒汉式"单例模式变成线程安全的呢?出现问题的关键在于if条件判断和new创建实例并不是原子操作,因此解决方法就是通过synchronized关键字将这两个操作打包成原子操作!

3.2.1 改进代码(解决线程安全问题)

/**
 * 改进版本单例模式(解决线程安全问题)
 */
class SingletonLazyImprove01 {
    private static SingletonLazyImprove01 instance = null; // 唯一实例
    private static Object locker = new Object();

    public static SingletonLazyImprove01 getInstance() {
        synchronized (locker) {
            if (instance == null) {
                instance = new SingletonLazyImprove01();
            }
        }
        return instance;
    }

    private SingletonLazyImprove01() {};
}

上述代码我们引入synchronized关键字将if条件判断与new创建实例操作加锁打包成"原子"操作,此时如果继续按照刚才的场景,线程t1先进行加锁,如果t2也尝试进入if语句块就会因为锁竞争而阻塞等待,直到线程t1创建完实例之后释放锁,此时t2线程判断实例已经被创建好,就直接返回,因此不会出现线程安全问题了!

3.2.2 改进代码(解决效率问题)

但是问题还没有结束!现在的代码虽然解决了线程安全问题,但是还存在着效率问题,因为我们这里调用getInstance都会先尝试加锁,然后判断当前实例是否为空,然后再解锁!但是线程安全问题只存在于第一次尝试创建实例的时候,如果实例已经创建完毕,后续所有的操作都是读取操作,就不会再产生线程安全问题了!所以我们引入的解决方式就是 if双重校验锁 ,其代码如下:

class SingletonLazyImprove02 {
    private static SingletonLazyImprove02 instance = null;
    private static Object locker = new Object();

    public static SingletonLazyImprove02 getInstance() {
        if (instance == null) {
            synchronized (locker) {
                if (instance == null) {
                    instance = new SingletonLazyImprove02();
                }
            }
        }
        return instance;
    }

    private SingletonLazyImprove02() {};
}

此时我们会看到有两个if判断语句,换做是单线程情况下我们从来不会写这样的代码,但是一旦涉及到多线程的时候,if双重校验锁是很常见的,其中第一个if语句用来判断是否需要进行加锁操作,第二个if语句用来判断是否需要创建实例,但是恰巧两个if语句的条件一样!

3.2.3 改进代码(解决指令重排序问题)

但是上述代码还有一些小问题,我们需要先介绍有关 指令重排序 的话题

指令重排序:指令重排序是一种编译器优化手段,会在保证逻辑不变的情况下调整原有代码的执行顺序,来提高程序的效率,但是有可能会引发线程安全问题
例如在创建实例的代码中:instance = new SingletonLazyImprove02();内部包括三个核心步骤

  1. 申请一块内存空间
  2. 调用构造方法对内存空间进行初始化
  3. 将内存空间地址赋值给instance变量

正常情况下执行顺序都是按照1->2->3进行的,但是编译器也可能会优化为1->3->2的步骤来执行,这两种在单线程环境下都没有问题,但是如果在多线程情况下执行就有可能出现线程安全问题
image.png
如图所示,线程t1先进行加锁,然后创建实例过程中先执行第一步与第三步,此时被调度出CPU,但是后来的线程判断实例是否为空,此时直接返回未被初始化的内存空间地址,这时就会出现错误!!!

volatile关键字:我们在之前的章节已经提到过使用volatile关键字可以解决内存可见性和指令重排序的问题,因此该问题的解决方式就是使用volatile关键字,强制让编译器完全按照1->2->3的顺序来执行

/**
 * 懒汉式单例模式改进版本(解决指令重排序)
 */
class SingletonLazyImprove03 {
    private static volatile SingletonLazyImprove03 instance = null;
    private static Object locker = new Object();

    public static SingletonLazyImprove03 getInstance() {
        if (instance == null) {
            synchronized (locker) {
                if (instance == null) {
                    instance = new SingletonLazyImprove03();
                }
            }
        }
        return instance;
    }

    private SingletonLazyImprove03() {};
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Linux学习笔记(centOS)—— 文件系统

目录 一、Linux中的文件 打开方式 二、目录结构​ 三、相关命令 切换目录命令 列出当前目录下的文件和目录命令 一、Linux中的文件 “万物皆文件。” 图1.1 所有文件 打开方式 图形化界面左上角的位置→计算机,打开以后就可以看到Linux全部的文件了&#xf…

Linux第43步_移植ST公司uboot的第4步_uboot测试

uboot移植结束后,需要进行测试。 1、烧录程序 1)、将STM32MP157开发板的网络接口与路由器的网络接口通过网线连接起来。 2)、将开发板的串口和电脑通过USB线连接起来。 3)、将开发板的USB OTG接口和电脑通过USB线连接起来。 4)、将开发板上拨码开关拨到“000”…

nodeJS 的 npm 设置国内高速镜像之淘宝镜像的方法

1、我们知道 nodeJS 是老外搞出来的,服务器放在了国外,国内的小朋友访问起来会比较慢,阿里巴巴的淘宝给出了有力支持,现在我们就将 nodeJS 的镜像地址切换为国内的淘宝镜像。 2、查看当前的镜像地址: npm get registr…

前端学习之路(6) npm详解

npm 是什么? npm(node package manager):node.js 的包管理器,用于node插件管理(包括安装、卸载、管理依赖等) ,npm 是随同 node.js 一起安装的包管理工具,能解决 node.j…

HTTP2:netty http2 StreamChannel多流实现与Http2StreamFrame解码器的源码分析

netty http2 server侧的核心逻辑个人认为,主要在编解码处理器和Stream Transform Channel这块,分别处理Http2 消息帧的编解码,以及连接的多流处理机制。对应用的处理类分别: ChannelHandlerDescio.netty.handler.codec.http2.Htt…

编译OpenSSL时报错,Can‘t locate IPC/Cmd.pm in @INC

编译OpenSSL 3.0.1时报错,错误信息如下 解决方法: 安装perl-CPAN yum install -y perl-CPAN进入CPAN的shell模式,首次进入需要配置shell,按照提示操作即可(本人perl小白,全部选择默认配置,高…

Python算法题集_环形链表

Python算法题集_环形链表 题234:环形链表1. 示例说明2. 题目解析- 题意分解- 优化思路- 测量工具 3. 代码展开1) 标准求解【集合检索】2) 改进版一【字典检测】3) 改进版二【双指针】 4. 最优算法 本文为Python算法题集之一的代码示例 题234:环形链表 …

FPGA高端项目:解码索尼IMX327 MIPI相机转USB3.0 UVC 输出,提供FPGA开发板+2套工程源码+技术支持

目录 1、前言免责声明 2、相关方案推荐我这里已有的 MIPI 编解码方案 3、本 MIPI CSI-RX IP 介绍4、个人 FPGA高端图像处理开发板简介5、详细设计方案设计原理框图IMX327 及其配置MIPI CSI RX图像 ISP 处理图像缓存UVC 时序USB3.0输出架构FPGA逻辑设计工程源码架构SDK软件工程源…

数学建模-灰色预测最强讲义 GM(1,1)原理及Python实现

目录 一、GM(1,1)模型预测原理 二、GM(1,1)模型预测步骤 2.1 数据的检验与处理 2.2 建立模型 2.3 检验预测值 三、案例 灰色预测应用场景:时间序列预测 灰色预测的主要特点是模型使用的…

改变AI服务器:探索界面互连芯片技术的创新突破

根据TrendForce的数据,AI服务器的出货量约为130,000台,占全球服务器总出货量的约1%。随着微软、Meta、百度和字节跳动等主要制造商相继推出基于生成式AI的产品和服务,订单量显著增加。预测显示,在ChatGPT等应用的持续需求推动下&a…

Java+微信小程序实现智慧家政系统 JAVA+Vue+SpringBoot+MySQL

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块三、系统展示四、核心代码4.1 查询家政服务4.2 新增单条服务订单4.3 新增留言反馈4.4 小程序登录4.5 小程序数据展示 五、免责说明 一、摘要 1.1 项目介绍 基于微信小程序JAVAVueSpringBootMySQL的智慧家政系统&#xff0…

TCP 传输控制协议

1 TCP 1.1 TCP 最主要的特点 1.TCP 是面向连接的运输层协议。 2.每一条 TCP 连接只能有两个端点 (endpoint),每一条 TCP 连接只能是点对点的(一对一)。 3.TCP 提供可靠交付的服务。 4.TCP 提供全双工通信。 5.面向字节流 TCP 中的“流…

redisson源码解析

由于synchronized跟ReetrantLock是JVM级别的锁,在分布式情况下失效,这时候我们通常会选择redisson基于redis封装好的分布式锁。下面我们一起来分析以下redisson的源码。 使用方式 流程 getLock源码 给命令执行器赋值给看门狗时间赋值,默认30…

【芯片设计- RTL 数字逻辑设计入门 11.1 -- 状态机实现 移位运算与乘法 1】

文章目录 移位运算与乘法状态机简介SystemVerilog中的测试平台VCS 波形仿真 阻塞赋值和非阻塞赋值有限状态机(FSM)与无限状态机的区别 本篇文章接着上篇文章【芯片设计- RTL 数字逻辑设计入门 11 – 移位运算与乘法】 继续介绍,这里使用状态机…

PCA与梯度上升法

PAC 主成分分析(Principal Component Analysis) 一个非监督的机器学习算法主要用于数据的降维通过降维,可以发现更便于人类理解的特征其他应用:可视化;去噪 如何找到这个让样本间间距最大的轴? 如何定义样…

【我与Java的成长记】之String类详解

系列文章目录 能看懂文字就能明白系列 C语言笔记传送门 Java笔记传送门 🌟 个人主页:古德猫宁- 🌈 信念如阳光,照亮前行的每一步 文章目录 系列文章目录🌈 *信念如阳光,照亮前行的每一步* 前言一、字符串构…

zabbix配置主动监控

1.准备一台新的主机,安装相关软件包。 [rootsishi ~]# rpm -Uvh https://repo.zabbix.com/zabbix/5.0/rhel/7/x86_64/zabbix-release-5.0-1.el7.noarch.rpm [rootsishi ~]# yum -y install zabbix-agent2.修改zabbix-agent端的配置文件 [rootsishi ~]# vim /etc/z…

图像处理入门:OpenCV的基础用法解析

图像处理入门:OpenCV的基础用法解析 引言OpenCV的初步了解深入理解OpenCV:计算机视觉的开源解决方案什么是OpenCV?OpenCV的主要功能1. 图像处理2. 图像分析3. 结构分析和形状描述4. 动态分析5. 三维重建6. 机器学习7. 目标检测 OpenCV的应用场…

SegmentAnything官网demo使用vue+python实现

一、效果&准备工作 1.效果 没啥好说的,低质量复刻SAM官网 https://segment-anything.com/ 需要提一点:所有生成embedding和mask的操作都是python后端做的,计算mask不是onnxruntime-web实现的,前端只负责了把rle编码的mask解…

【MacOS】装 mac-win10 双系统(2017年的老mac,Intel芯片)

Navigator 一、前情二、完整过程2.1 Mac系统迁移2.2 分区合并2.3 下载win10镜像2.4 安装win102.5 安装驱动等2.6 设置默认启动系统 一、前情 昨天给学妹的mac装软件。发现之前她找维修店装了双系统,但是win10根本不能用,搞得乱七八糟的,于是…