当前位置: 首页 > news >正文

【Java多线程从青铜到王者】懒汉模式的优化(九)

懒汉模式的问题

在这里插入图片描述
我们看上述的代码,当第一次调用getIntance的时候,intance为null,就会进入if里面,创建出实例,当不是第一次调用的时候,此时的intandce不是null,不进入循环,直接return之前实例好的对象,这样的设定仍然可以保证我们的实例是唯一的,但是创建的时机不一样了,再也不是从程序刚开始驱动的时候开始创建了,而是第一次调用getIntance时开始创建,这个操作的创建时机就不确定了,根据实际的需求,大概率会比饿汉模式要慢一些,也许你整个程序里面就用不到这个方法,于是就省下了创建的开销
有的程序,可能需要根据一定的条件,来判断是否要执行某些操作,是否要实例化实例化一些对象,比如肯德基的疯狂星期四,程序就会判断今天是星期几,根据今天是星期几来判断是否要执行星期四的相关逻辑,如果不是星期四,就不需要执行星期四的相关逻辑了(节省了开销)
我们介绍的单例模式有两种,一个是饿汉模式,一个是懒汉模式,那么问题来了,我们所写的两个代码是否是线程安全的?
在这里插入图片描述
我们先看单例模式(上图),我们的代理模式的实例在程序启动的时候就创建好了,我们调用getIntance方法只是返回我们已经创建好的对象,可以看作是读操作,我们多个线程同时读取同一个对象是不会有线程问题的
在这里插入图片描述
我们再看懒汉模式(上图),if里面的new操作可以看成是写操作,return操作可以看成是读操作,我们在内存可见性那里就讲过了,如果一个线程写的话,同时一个线程读的话,是会有线程安全问题的
在这里插入图片描述
如果有两个线程的执行顺序是上图的样子,就会导致new了两次,我们期望是只有唯一的一个实例,如果是这种情况的话,代码就出现了bug
如何改进懒汉模式呢?
加锁,sychronized
在这里插入图片描述
上图中的代码,我们已经加了锁,多线程的代码非常的复杂,随便改动一点,结果可能都不一样,不一定你加了锁就会线程安全,不加就一定会线程不安全,要根据具体的代码进行分析,一定要确保我们的代码在任意顺序下执行的结果都是正确的,我们此处要是想要代码执行正确的话,是要将if和new两个操作打包成原子的
要不然上图的代码在下图的情况中还是会有线程安全问题
在这里插入图片描述
下图中的加锁方式,就不会出现线程安全问题
在这里插入图片描述
当我们出现上面提到的线程不安全的情况的时候,上图的加锁方式就可以保证线程安全(如下图)
在这里插入图片描述
这样就可以确保,一定是t1执行完new,并且修改了intance的值之后吗,再去执行t2的if,此时t2的if就不会成立了,就不会出现重复new的情况了
我们解决了线程安全的问题,但是我们的代码还是存在一些问题
如果我们已经创建好了intance的话,我们再调用getIntance方法的话,执行的逻辑就都是return intance了,此时就是存粹的读操作了,跟我们的饿汉模式里面的getIntance方法就一样了,此时就没有线程安全问题了,对于一个没有线程安全问题的代码我们进行加锁的话,就会导致效率变低了,为什么效率会变低呢?我们的代码里只要涉及到加锁的问题的话,就会产生因为锁竞争产生的阻塞,阻塞什么时候解除我们不得而知,所以一旦我们的代码里面加了锁,基本就注定于“高性能”无缘了
我们的解决办法是什么呢?
在锁的外面再加上一层if,判断一下intance是否为空,如果为空,说明是第一次调用getInatance,我们执行加锁操作,如果intance为空的话,就说明不是第一次调用,只需要执行return操作(纯粹的读)即可,通过这个if,我们就可根据intance的值的情况判断是否需要进行加锁操作,代码如下图
在这里插入图片描述
但是我们注意到,这个代码我们从来没有见过,这里两个if里面的条件是一样的(如下图)
在这里插入图片描述
在我们之前的单线程/没有阻塞的代码中,两个相同条件的if是没有意义的(只需要写一个即可),但是我们现在的代码涉及到了多线程和阻塞,看起来是两个相同条件的if,其实两个条件的结果是可能不一样的,因为两个if之间有一个加锁的操作,这个加锁可能会导致阻塞,这个阻塞就可能导致intance的值被改变了,两个if之间隔的时间是沧海桑田

第一个if判断是否要加锁
第二个if判断是否要new
两个if的条件虽然一样,但是意义不一样

此时我们已经解决了很多的问题,但是这个代码还是有问题——指令重排序引起的线程安全问题

指令重排序就是在原有逻辑不变的情况下,改变代码的执行顺序,提高程序的效率
在这里插入图片描述

我们上图的new操作就可以拆分成三个大步骤(不是三个指令)
1.申请一段内存空间
2.在这个申请好的内存空间里面调用构造方法,创造出这个实例
3.将这个内存空间的地址赋值给intance
正常情况下,我们的执行顺序是1 2 3,但是编译器也可能会将将这三个步骤的顺序优化成1 3 2,这两种顺序在单线程的情况下都是一样的
但是在多线程的情况下就出现了问题了

在这里插入图片描述
这个代码就会出现上面的问题
想要解上面的问题,还是得使用volatile解决

volatile由两个功能
1.保持内存可见性,每次访问变量必须从内存里面读取,不会优化成从寄存器/缓存里面读取
2.禁止指令重排序,这个被volatile修饰的变量的读写的相关操作的相关指令是不能被重排序的

完整代码如下

class Singleton1{private volatile static Singleton1 intance=null;private static Object locker=new Object();public static Singleton1 getIntance(){if(intance==null){synchronized (locker){if(intance==null){intance=new Singleton1();}}}return intance;}private Singleton1(){}
}

相关文章:

  • 【GESP真题解析】第 2 集 GESP 四级样题卷编程题 1:绝对素数
  • IK分词器
  • 模型训练-关于token【低概率token, 高熵token】
  • Q: dify的QA分段方式,question、answer和keywords哪些内容进入向量库呢?
  • Python主动抛出异常详解:掌握raise关键字的艺术
  • 力扣HOT100之堆:347. 前 K 个高频元素
  • TDengine 快速体验(云服务方式)
  • 对3D对象进行形变分析
  • 3D扫描技术赋能汽车零部件尺寸测量效率提升
  • win11本地Docker部署腾讯云Docker部署若依前后端分离版
  • 关于前端常用的部分公共方法(三)
  • Spring boot应用监控集成
  • 基于算法竞赛的c++编程(28)结构体的进阶应用
  • 渗透靶场PortSwigger Labs指南:规范链接的反射XSS
  • JavaScript 核心对象深度解析:Math、Date 与 String
  • CPP基础(2)
  • 在多云环境透析连接ngx_stream_proxy_protocol_vendor_module
  • 最新SpringBoot+SpringCloud+Nacos微服务框架分享
  • 72道Nginx高频题整理(附答案背诵版)
  • Vue.js教学第二十二章:vue实战项目商城项目
  • 学校网站开发背景/seo优化自动点击软件
  • 租用网站服务器价格/百度指数人群画像
  • 传媒公司网站建设方案/搜索引擎营销的主要方法包括
  • 桐城网站定制/百度推广方式有哪些
  • 小说网站怎么建设的/5118网站如何使用免费版
  • 做网站除了买域名还有什么/seo整站优化报价