【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() {};
}

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

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

相关文章

python的数据类型

🎈srting(字符串): 操作符: :字符串连接 aabc befg print(ab) #输出 abcdefg * : 重复输出字符串 aabc print(a*3) #输出 abcabcabc [ : ]:截取字符串中的一部分,遵循左闭右开的原则&am…

李宏毅LLM——大模型+大资料的神奇力量

文章目录 大模型的重要性顿悟时刻 大资料的重要性数据预处理不一样的做法:KNN LM 对应视频P12-P14 大模型的重要性 模型参数和数据集越大,文字接龙的错误率越低 顿悟时刻 当模型超过10B-20B时,会突然顿悟 启示:不能只看最终结…

黑马Java——集合进阶(List、Set、泛型、树)

一、集合的体系结构 1、单列集合(Collection) 二、Collection集合 1、Collection常见方法 1.1代码实现: import java.util.ArrayList; import java.util.Collection;public class A01_CollectionDemo1 {public static void main(String[] a…

寻迹模块——红外循迹模式使用介绍

目录 循迹模式——红外循迹模式使用介绍 红外循迹模块介绍 接线 循迹小车原理 安装与接线 实验程序 实验效果 循迹模式——红外循迹模式使用介绍 实验效果: 寻迹模块-CSDN直播 红外循迹模块介绍 传感器的红外发射二极管不断发射红外线,当发射出…

[C/C++] -- Boost库、Muduo库编译安装使用

1.Muduo库 Muduo 是一个基于 C11 的高性能网络库,其核心是事件驱动、非阻塞 I/O、线程池等技术,以实现高并发、高性能的网络通信。Muduo 库主要由陈硕先生开发维护,已经成为 C 服务器程序员的常用工具之一。 Muduo 库的主要特点&#xff1a…

前端工程化之:webpack2-2(内置插件)

目录 一、内置插件 1.DefinePlugin 2.BannerPlugin 3.ProvidePlugin 一、内置插件 所有的 webpack 内置插件都作为 webpack 的静态属性存在的,使用下面的方式即可创建一个插件对象: const webpack require("webpack")new webpack.插件…

Web3智能合约:重新定义商业合作的未来

随着区块链技术的飞速发展,Web3时代正逐渐到来,而其中的智能合约成为推动商业合作变革的关键力量。本文将深入探讨Web3智能合约的概念、特点以及对商业合作未来的巨大影响。 什么是Web3智能合约? 智能合约是一种以代码形式编写、自动执行合同…

React+Echarts实现数据排名+自动滚动+Y轴自定义toolTip文字提示

1、效果 2、环境准备 1、react18 2、antd 4 3、代码实现 原理:自动滚动通过创建定时器动态更新echar的dataZoom属性startValue、endValue,自定义tooltip通过监听echar的鼠标移入移出事件,判断tooltTip元素的显隐以及位置。 1、导入所需组…

【考研数学】选汤家凤1800 还是 张宇1000❓关键看这一点

考研备考,如果没有准备好,真的不要随便开始,因为已经有人开始后悔了! 特别是关于考研数学,很多人都不知道该如何刷题,如何选资料,下面我就分享一下我的经验: 关于考研做题&#xf…

RobotFramework报错都是因为什么

1、参数问题FAILKeyword common. Bpm Ui Query Delete Data expected 44 arguments,got 3. 这种报错的意思是,应该有4个参数,实际只展示了3个参数 找对应的解决方案一 可能是入参的时候数量不一致 解决方案二: 对应的参数中间有空格 …

机器学习 | 一文看懂SVM算法从原理到实现全解析

目录 初识SVM算法 SVM算法原理 SVM损失函数 SVM的核方法 数字识别器(实操) 初识SVM算法 支持向量机(Support Vector Machine,SVM)是一种经典的监督学习算法,用于解决二分类和多分类问题。其核心思想是通过在特征空间中找到一…

Java小区物业管理系统

技术架构: springboot mybatis thymeleaf Mysql5.7 有需要该项目的小伙伴可以私信我你的Q。 功能描述: 控制台、数据库、楼栋管理、单元管理、房屋管理、车位管理、缴费类型、缴费管理、公告管理、维修管理、投诉管理、用户管理 效果图&#xff…