【JavaEE初阶系列】——多线程案例一——单例模式 (“饿汉模式“和“懒汉模式“以及解决线程安全问题)

目录

🚩单例模式

🎈饿汉模式

🎈懒汉模式

❗线程安全问题

📝加锁

📝执行效率提高

📝指令重排序

🍭总结 


单例模式,非常经典的设计模式,也是一个重要的学科,也是程序员必备的技能。

设计模式其实就是程序员的棋谱,开发过程中,会遇到”经典场景“,针对这些经典场景,

🚩单例模式

单例实际上是单个实例(对象),这种场景种,希望有的类,只能有一个对象,不能有多个,再这种场景下,就可以使用单例模式了。

程序员不能手动自己设置一个单个对象,确实可以,但是编译器不相信你,需要我们做监督,确保这个对象不会出现多个(出现多个的时候直接编译报错) 比如我们前期学到的 final ,interface,@Override,throw等等,都是涉及到这里的思想方法。


🎈饿汉模式

类加载的时候,创建实例

  • 在类的内部,提供一个现成的实例。
  • 把构造方法设为private,避免其他代码能够创建出实例。

通过上述方式,就强制了其他程序员在使用这个类的时候,就不会创建出多个对象了。

class SingTon{
    private static SingTon instance=new SingTon();
    //后续如果需要得到这个实例,那么就可以直接调用getInstance()方法
    public static SingTon getInstance(){
        return instance;
    }
    //给构造方法设置成私有的,此时类外面的其他代码,就无法new其他实例了
    private SingTon(){};
}

 得到实例的方法是被static修饰的,所以只用依赖类来。

但是如果你创建对象的时候,因为构造方法是私有的,也是无法创建的。

所以这样就真正做到了"饿汉模式“的单例模式.


🎈懒汉模式

非必要,不创建实例,等需要了,再创建

class SingLazy{
    private static SingLazy instance=null;
    public static SingLazy getInstance(){
        //首次调用getInstace()方法的时候才是创建
        if(instance==null){
            instance=new SingLazy();
        }
        return instance;
    }
    private SingLazy(){};
}

首先我们先不创建对象,其指向空,如果instace是null,那么我们创建对象,如果不是空,那么就直接返回instace。


其实"懒”也是意味着高效率,省略了一些不必要的操作,比如去上个厕所,顺便去倒杯水喝。而不是想喝水立即去喝水。

就比如文本编译器(记事本)比如需要打开一个非常大的文件(10gb)

  • 1.先把所有的内容,都加载到内存中,然后再显示内容(加载过程会很慢)
  • 2.只加载一小部分数据到内存,立即显示内容,随着用户翻页,再加载其他内容(懒汉)

介绍完懒汉模式和饿汉模式是如何实现单例模式的。

接下来我们来探究探究”懒汉模式“和”饿汉模式”俩种模式在线程安全中是否是安全的!


❗线程安全问题

📝加锁

这俩种写法,是否有线程安全问题呢?(如果多个线程,同时调用getInstance,是否会出问题呢?)

这俩种方式,有一个是线程安全的,一个是不安全的。

  • 如果多个线程,同时修改同一个变量,此时就可能出现线程安全问题。
  • 如果多个线程,同时读取同一个变量,这个时候就没事~不会有线程安全问题。

我们之前学到了,再多线程中对同一个变量进行修改的时候,这时候会出现线程安全问题。

这个时候,实例已经是多个了,违背了单例的要求。

一旦这俩操作被穿插了,就容易出现问题,加锁的关键是要保证这俩操作是一个整体


那加锁的位置是在哪呢?

一个加锁new是创建对象,第二个加锁是将if和new的都加锁了。锁不是加了就线程安全,加的对不对,非常关键。

  • 1>锁的{}范围是合理的,能够把需要作为整体的每个部分都囊括进去
  • 2>锁的对象,也得是能够起到合理的锁竞争的效果。

因为我们上述的线程中因为t1线程if成立了,然后t2线程进行if和new操作,此时new操作完了后t1线程剩下的部分继续进行,我们只给new的部分加锁,那么就依旧存在线程安全问题。我们需要将if 和new操作整体都加上锁,才会避免穿插的情况。

但是一旦代码这样写,后续每次调用getInstace,就需要先加锁了,但是实际上,懒汉模式,线程安全问题,只是出现在最开始的时候(对象没有new的情况),一旦对象new出来了,后续多线程调用getInstace,就只有读操作,就不会线程不安全了。其实加锁是一个开销很大的操作,加锁就可能涉及到锁冲突的问题,一冲突就会引起阻塞等待了,某个代码涉及到加锁,其实这个代码和高性能就冲突了。

如果多个线程情况下,第一次对象是null,此时创建好对象之后,其他线程阻塞等待,然后后面线程继续进行,然后一直加锁,if判断不成立,就进行解锁,然后其他线程又加锁,这样如果有一百个线程进行,那么就会有一百次加锁的情况,那样性能方面是开销很大的。


📝执行效率提高

有没有什么办法,既可以让代码线程安全,又不会对执行效率产生太多的影响呢?

在加锁语句的外层,再引入一个if条件,判定一下,看看当前这里的锁,是否要加上。

  • 如果对象已经有了,线程就安全了,就不用加锁了。
  • 如果对象还没有,存在线程不安全的风险,就需要加锁。
   if(instance==null){
      //首次调用getInstace()方法的时候才是创建
        synchronized (SingLazy.class) {
           if(instance==null){
               instance=new SingLazy();
             }
           }
        }

同样的条件连续写俩遍,在别的地方没啥意义,但是这个代码是非常有意义的,也是非常重要的,防止上述的执行效率很低。第一个if用来判定是否需要加锁,第二个if用来判定是否需要new对象。

就是说第二个if确保只有一个线程去创建实例,第一个if确保其他线程直接拿这个实例就行,不用每次都在那一直傻傻等待。t1线程俩个if都判断成立了,然后t2线程第一个if都进不去,因为已经创建好对象了(是否需要继续加锁)。


📝指令重排序

指令重排序也可能会出现对上述的问题影响。编译器为了执行效率,可能会调整原有代码的执行顺序,调整的前提是要保持逻辑不变。

通常情况下,指令重排序,就能够保证逻辑不变的前提下,把程序执行效率大幅度提高。(单线程下好办,多线程下,可能会出现误判)

 new操作,是可能会触发指令重排序的。

new操作可以拆分成三步:

  • 1.申请内存空间
  • 2.在内存空间上构造对象(构造方法)
  • 3.把内存的地址,赋值给instance引用

可以按照1,2,3来执行,也可以按照1,3,2来执行(但是1肯定是执行的)。

但是在多线程的情况下,就可能有问题了。假设是按132执行的,当t1执行完1和3时候,此时Instance就已经非空了!!但是此时Instance指向的是一个还没初始化的非法对象

此时此刻,还没执行2呢,t2就开始执行了,t2判定instance==null,条件不成立,于是t2就直接return instance。进一步的t2线程的代码就可能会访问instance里面的属性和方法了。

但是instance是一个未初始化的非法对象,如果t2线程访问的话就会出现bug。

这就相当于买房子的时候,第一步是买房子,第二步装修,第三步是交钥匙,最后是一个精装房,但是如果我们按照这个顺序第一步是买房子,第二步就交钥匙了,打开之后只是一个毛胚房。

解决的方法就是我们之前学到的是volatile,可以避免指令重排序问题。让volatile修饰Instance,此时就可以保证Instance在修改过程中就不会出现执行重排序的现象了。

class SingLazy{
    private static volatile SingLazy instance=null;
    public static SingLazy getInstance(){
        if(instance==null){
            //首次调用getInstace()方法的时候才是创建
            synchronized (SingLazy.class) {
                if(instance==null){
                    instance=new SingLazy();
                }
            }
        }
        return instance;
    }
    private SingLazy(){};
}

 这样就解决了在创建对象的时候,编译器优化的时候,直接执行分配内存空间和把内存的地址,赋值给insatance引用。但是中间的在内存空间中创建对象的一步直接被编译器优化了,就不执行了。然后最后别的线程在调用的时候判断不成立, 直接返回instance就会是一个没初始化的非法对象。如果用volatile修饰,那么三步都操作,没有编译器优化的现象了。


🍭总结 

在最开始的时候,

一、多线程的情况下对同一个变量进行修改会出现线程安全的问题,之后我们就需要加锁,让其他线程阻塞等待,

二、加锁的时候我们要注意到,if和new俩个操作都得统一加锁在一起,如果只给new加锁的话,也依旧会出现问题。

三、加完锁之后,我们发现线程t1判断之后instance不为空,然后其他线程继续加锁,不为空null,然后解锁,然后阻塞等待的线程继续加锁,如果有一百个线程,那么就有一百次加锁。这样会使执行效率降低,所以我们就继续判断if,这个if和内层的if判断的条件是一样的,但是意义是不一样的,第一个if是判断是否需要加锁,第二个if是判断是否创建这个对象

四、我们还要考虑到指令重排序问题,因为new操作会有三步,分配内存空间,让内存空间构造方法(创建对象),内存的地址赋值给instance引用,但是编译器会优化,不进行内存空间构造方法,直接分配完空间之后,直接赋值给instance引用。这样就导致了t1线程拿到的instance是一个未初始化的非法对象但是非null,t2线程再继续进入俩层if不为空,这样就返回了未初始化的非法对象,这样就导致了bug,就得需要用volatile修饰


日子是自己的,你开心,它就会幸福。

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

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

相关文章

007 日期类型相关工具类

推荐一篇文章 http://t.csdnimg.cn/72F7Jhttp://t.csdnimg.cn/72F7J

git配置密钥

要配置 Git 密钥,可以按照以下步骤进行操作: 1.生成密钥:首先,在终端或命令提示符中运行以下命令生成密钥对: ssh-keygen -t rsa -b 4096 -C "dengweng-pulse.net"这将生成一个 RSA 密钥对,其中…

clickhouse学习笔记02(小滴课堂)

ClickHouse核心基础-常见数据类型讲解 插入数据: decimal类型的数据,整数部分超了会报错,小数部分超了会截取。 查看表结构: 查询: 插入: 更新操作: 这个和mysql的语句不太一样。 删除语句和my…

第十二届蓝桥杯JavaB组省赛真题 - 货物摆放

解题思路&#xff1a; 暴力 优化前&#xff08;代码没有错&#xff0c;但会超时&#xff09;&#xff1a; import java.util.*;public class Main {public static void main(String[] args) {long n 2021041820210418L;long cnt 0;for (long a 1; a < n; a) {for (lon…

【Java程序设计】【C00364】基于Springboot的美发管理系统(有论文)

基于Springboot的美发管理系统&#xff08;有论文&#xff09; 项目简介项目获取开发环境项目技术运行截图 项目简介 项目获取 &#x1f345;文末点击卡片获取源码&#x1f345; 开发环境 运行环境&#xff1a;推荐jdk1.8&#xff1b; 开发工具&#xff1a;eclipse以及idea&…

第一天工作我的

工作的第一件事情打开文件信息 开始工作了 太过分了 启动 出现了这样的错误 我这里写的有什么问题吗? computed: {getGroundCtcOptions() {var dis [];for (let i in ctcDataRef.value) {dis.push({label: ctcDataRef.value[i].name,value: ctcDataRef.value[i].id,});}retur…

iPhone 16将接入百度AI功能

关注卢松松&#xff0c;会经常给你分享一些我的经验和观点。 ​百度接了个大活儿&#xff0c;将为国行iPhone 16提供AI功能。这消息一出&#xff0c;基本可以确立百度在AI界领头羊的地位了。百度这些年一直在无人驾驶、AI大模型方面发力&#xff0c;看来还是有成就的&#xf…

第十二届蓝桥杯JavaB组省赛真题 - 路径

解题思路&#xff1a; 动态规划 需要熟练掌握最小公倍数和最大公约数的计算 import java.util.*;public class Main {public static void main(String[] args) {int[] dp new int[2022];dp[1] 0;for (int i 2; i < 2021; i) {dp[i] Integer.MAX_VALUE;}for (int i 1…

【MATLAB源码-第13期】基于matlab的4ASK的误码率BER和误符号率SER理论和实际对比仿真。

操作环境&#xff1a; MATLAB 2022a 1、算法描述 "4ASK" 是一种数字调制技术&#xff0c;代表4级振幅移移键控&#xff08;4-Level Amplitude Shift Keying&#xff09;调制。它是一种数字通信中常用的调制方式之一&#xff0c;用于将数字信号转换为模拟信号以便传…

《量子计算:揭开未来科技新篇章》

随着科技的不断发展&#xff0c;量子计算作为一项颠覆性的技术逐渐走进人们的视野&#xff0c;引发了广泛的关注和探讨。本文将围绕量子计算的技术进展、技术原理、行业应用案例、未来趋势预测以及学习路线等方向&#xff0c;深入探讨这一领域的前沿动态和未来发展趋势。 量子…

c++初阶------c++代码模块

作者前言 &#x1f382; ✨✨✨✨✨✨&#x1f367;&#x1f367;&#x1f367;&#x1f367;&#x1f367;&#x1f367;&#x1f367;&#x1f382; ​&#x1f382; 作者介绍&#xff1a; &#x1f382;&#x1f382; &#x1f382; &#x1f389;&#x1f389;&#x1f389…

档案室升级改造基建方面需要考虑哪些问题

升级和改造档案室可能需要以下材料&#xff1a; 1. 墙壁和地板材料&#xff1a;选择耐用、易于清洁的材料&#xff0c;如瓷砖、大理石、地板、木材或维护低的地毯等。 2. 墙体材料&#xff1a;可能需要新的墙壁材料来分隔出更多的空间&#xff0c;例如石膏板、砖块或玻璃隔断等…

R语言神经网路模型应用(1)

数据集heart_learning.csv与heart_test.csv是关于心脏病的数据集&#xff0c;heart_learning.csv是训练数据集&#xff0c;heart_test.csv是测试数据集。要求&#xff1a;target和target2为因变量&#xff0c;其他诸变量为自变量&#xff0c;用神经网络模型&#xff08;多层感知…

javaWeb私人牙科诊所管理系统

一、摘要 随着科技的飞速发展&#xff0c;计算机已经广泛的应用于各个领域之中。在医学领域中&#xff0c;计算机主要应用于两个方面&#xff1a;一是医疗设备智能化&#xff0c;以硬件为主。另一种是病例信息管理系统&#xff08;HIS&#xff09;以软件建设为主&#xff0c;以…

MySQL 高级语句(一)

一、MySQL查询 1.1 排序 ASC 是按照升序进行排序的&#xff0c;是默认的排序方式&#xff0c;即可以省略&#xff1b; SELECT语句中如果没有指定具体的排序方式&#xff0c;则默认按ASC方式进行排序。 DESC是按降序方式进行排列。当然ORDER BY前面也可以使用 WHFRE 子句对查…

23.异步模式-生产者、消费者

1.与保护性暂停GuardedObject不同&#xff0c;不需要产生结果与消费结果的线程一一对应。 2.消费队列可以用来平衡生产和消费的线程资源。 3.生产者负责产生结果数据&#xff0c;不关心数据该如何处理&#xff0c;而消费者专心处理结果数据。 4.消息队列是有容量限制的&…

风险评估在网络安全领域的应用与实践

一、引言 在数字化浪潮席卷全球的今天&#xff0c;网络安全已成为企业运营和发展的核心问题。随着信息技术的快速发展&#xff0c;企业面临着日益复杂的网络安全威胁&#xff0c;如黑客攻击、数据泄露、恶意软件等。这些威胁不仅可能导致企业重要信息的丢失或泄露&#xff0c;…

2024 年 5 款适用于 Linux 的参考文献管理软件

时间是宝贵的&#xff0c;因此如果某款软件能让您摆脱必须执行的日常繁琐任务&#xff0c;那么它就会派上用场。 参考文献管理工具就是此类软件的典型代表&#xff0c;只需点击几下就能自动格式化引文。学生、教育工作者、作家、科学家和研究人员一定会发现它们非常有用。 在…

各城市宗族文化姓氏占比数据

各城市宗族文化姓氏占比数据 1、指标&#xff1a;省份代码、所属省份、城市代码、所属城市、第1大姓氏、第2大姓氏、第3大姓氏、宗族文化强度 2、方法说明&#xff1a; 根据2005年全国1%的人口调查数据计算。其中第1大姓氏第一大姓人口数/总人口数&#xff0c;宗族文化强度(…

华院计算荣获CSDN“创新企业”和“年度创新产品与解决方案”大奖

日前&#xff0c;全国最大的专业开发者社区CSDN发布“2023中国开发者影响力年度榜单”&#xff0c;华院计算凭借其卓越的认知智能引擎平台荣获“创新企业”和“年度创新产品与解决方案”两项大奖。 CSDN 以数据为基础&#xff0c;经过个人或企业提交资料、层层筛选、深入调研、…