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

【spring】从spring是如何避免并发下获取不完整的bean引发的思考 什么是双重检查锁 什么是java内存模型

本文将通过简述spring是如何避免并发下获取不完整的bean,延伸出双重检查锁、volatile、JMM的概念,将这些知识点都串联起来;
若发现错误,非常欢迎在评论区指出;csdn博主:孟秋与你

文章目录

  • 双重检查锁(Double-Checked Locking)
  • java内存模型(Java Main Memory)
    • 内存模型三大特性:原子性、可见性、顺序性
    • volatile
    • happen before原则
    • 完善单例模式+double checked
  • spring如何做到避免并发下获取不完整的bean

双重检查锁(Double-Checked Locking)

这是一个非常古老且经典的概念,在单例模式中会用到;我们可以看看这段经典的demo代码:

下面是创建单例模式的一段代码,在单线程中 不会有问题

public class Singleton {  // 加上 volatile 关键字private volatile static Singleton instance = null;  private Singleton(){}  public static Singleton  getInstance() {  // do something or checkif(instance == null) {  instance = new Singleton();  }  return instance;  }  
}  

但是在多线程,尤其高并发下,极容易引发问题, 我们可以设想一下:
thread1和thread2同时进入getInstance()方法,同时执行if(instance == null)校验,那么两个线程执行的判断都为true,都会创建实例;

如果将整个方法加上 synchronized 锁,问题解决了,但是锁整个方法 锁的粒度太大了一点,我们只需要保证创建实例的时候上锁即可

所以我们将代码稍微调整一下:

public class Singleton {  private static Singleton instance = null;  private Singleton(){}  public static Singleton  getInstance() {  if(instance == null) {  // do something or checksynchronized(Singleton.class){if(instance == null) {instance = new Singleton(); }}}  return instance;  }  
}  

上诉代码仍会有问题,因为初始化Singleton 和 将对象地址写到instance字段 的顺序是不确定的,所以可能出现在某个线程new Singleton()时,在构造方法被调用之前,就为该对象分配了内存空间并将对象的字段设置为默认值。

接下来,我们简单讲述一下java内存模型的三大特性。

java内存模型(Java Main Memory)

java内存模型 简称JMM,在并发编程的艺术这本书里面会提到,JMM是一个抽象的概念,通俗点说 内存模型是我们用于理解cpu的操作提出来的一个概念,便于我们理解,但其实细节不一定是这么做的。
(对java开发者来说 理解止步于此即可,不用咬文嚼字, 把这个抽象概念当成理论就行了;换句话说只有抬杠的时候才可能指出 JMM是个抽象概念)

内存模型三大特性:原子性、可见性、顺序性

原子性:经典的 i++ 操作就不是原子性的,volatile不能完全当成锁来用 非常大的一个原因就是volatile不保证原子性

volatile int a = 1;
// 伪代码
public void test(){fori{// 会有线程安全问题i++;}
}

可见性: 多个线程之间可以看到状态已经被修改,线程安全中,可见性是一个非常非常重要的概念

顺序性:通俗点理解,我们写的代码顺序 cpu却并不一定是这么执行的,它在(自认为)最终结果不变的前提下发生指令重排;单线程下,指令重排是一种优化,cpu以它自己觉得效率更高的指令顺序来执行;但是如果在多线程下,指令重排就可能会导致问题,我们上一节提到的 “初始化Singleton 和 将对象地址写到instance字段 的顺序是不确定的” ,正是发生了指令重排。

volatile

轻量锁,volatile不能保证原子性,但是可以保证可见性、顺序性,所以在简单的结构里面,可以当成锁来用。
(在博主其它博客有提到 主页搜volatile可以看到)

volatile保证顺序性是因为阻止到了指令重排,我们说过 指令重排是cpu以它自己的方式来排序的,但这种方式肯定是不能乱来的,需要遵循一定的原则,这个原则就称为:happen before

happen before原则

程序顺序规则:在一个线程内,按照代码的顺序,前面的操作会在后面的操作之前发生。监视器锁规则:对一个锁的解锁操作,happen-before任何后续对同一个锁的加锁操作。volatile变量规则:对一个volatile变量的写操作,happen-before任何后续对该变量的读操作。线程启动规则:对线程的start()方法的调用,happen-before该线程开始运行。线程终止规则:线程的join()方法的返回,happen-before该线程的所有操作都完成。其他:还有一些其他规则,例如对同一个对象的操作顺序、各个线程之间的操作等。

完善单例模式+double checked

在有了以上概念后,回到单例模式那个例子,我们怎么解决
“初始化Singleton 和 将对象地址写到instance字段 的顺序是不确定的” 这个问题呢?

加上 volatile 关键字即可,代码如下:

public class Singleton {  private volatile static Singleton instance = null;  private Singleton(){}  public static Singleton  getInstance() {  if(instance == null) {  // do something or checksynchronized(Singleton.class){if(instance == null) {instance = new Singleton(); }}}  return instance;  }  
}  

spring如何做到避免并发下获取不完整的bean

tips: 基础概念: bean在实例化的时候 会放入三级缓存,然后进行一些属性注入 ,放入二级缓存,最后bean彻底创建后,放入一级缓存;放入一级缓存后 会将二三级缓存清除。
getBean发现为null时,会doCreateBean

我们可以想象下述场景:

线程1创建bean: 放入三级二级缓存,最后创建完成放入一级缓存

线程2会先读取一级缓存 此时线程1还没有存放至一级缓存 所以为空,等到读取二级三级缓存时 仍然可能出现为空的情况
(比如此时线程1存入了一级缓存 存放一级缓存会将二级、三级缓存清空)
如果为空,将会再次从一级缓存进行读取(double check)

博主比较喜欢用自己的理解和语言来描述,下面是画的一个简图 ,简陋但应该能看懂,网上也有专业的图 各位可以自行搜索。
在这里插入图片描述

注意:以上结论并不是严谨的结论,实际spring处理bean非常非常的复杂!

源码位于DefaultSingletonBeanRegistry 类的getSingleton方法

启动项目打断点会发现先执行的以下方法:
从代码来看

    @Nullablepublic Object getSingleton(String beanName) {return this.getSingleton(beanName, true);}@Nullableprotected Object getSingleton(String beanName, boolean allowEarlyReference) {Object singletonObject = this.singletonObjects.get(beanName);if (singletonObject == null && this.isSingletonCurrentlyInCreation(beanName)) {singletonObject = this.earlySingletonObjects.get(beanName);if (singletonObject == null && allowEarlyReference) {synchronized(this.singletonObjects) {singletonObject = this.singletonObjects.get(beanName);if (singletonObject == null) {singletonObject = this.earlySingletonObjects.get(beanName);if (singletonObject == null) {ObjectFactory<?> singletonFactory = (ObjectFactory)this.singletonFactories.get(beanName);if (singletonFactory != null) {singletonObject = singletonFactory.getObject();this.earlySingletonObjects.put(beanName, singletonObject);this.singletonFactories.remove(beanName);}}}}}}return singletonObject;}
public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {Assert.notNull(beanName, "Bean name must not be null");synchronized(this.singletonObjects) {Object singletonObject = this.singletonObjects.get(beanName);if (singletonObject == null) {if (this.singletonsCurrentlyInDestruction) {throw new BeanCreationNotAllowedException(beanName, "Singleton bean creation not allowed while singletons of this factory are in destruction (Do not request a bean from a BeanFactory in a destroy method implementation!)");}if (this.logger.isDebugEnabled()) {this.logger.debug("Creating shared instance of singleton bean '" + beanName + "'");}this.beforeSingletonCreation(beanName);boolean newSingleton = false;boolean recordSuppressedExceptions = this.suppressedExceptions == null;if (recordSuppressedExceptions) {this.suppressedExceptions = new LinkedHashSet();}try {singletonObject = singletonFactory.getObject();newSingleton = true;} catch (IllegalStateException var16) {IllegalStateException ex = var16;singletonObject = this.singletonObjects.get(beanName);if (singletonObject == null) {throw ex;}} catch (BeanCreationException var17) {BeanCreationException ex = var17;if (recordSuppressedExceptions) {Iterator var8 = this.suppressedExceptions.iterator();while(var8.hasNext()) {Exception suppressedException = (Exception)var8.next();ex.addRelatedCause(suppressedException);}}throw ex;} finally {if (recordSuppressedExceptions) {this.suppressedExceptions = null;}this.afterSingletonCreation(beanName);}if (newSingleton) {this.addSingleton(beanName, singletonObject);}}return singletonObject;}}

我们自己调试的时候就会发现,不同的bean情况不一样,处理非常非常复杂!

关于这个问题,博主推荐的答案为:

在源码getSingleton方法中,用到了双重检查锁,
先从一级缓存查找 ,找不到会去二级或三级查找, 
如果再找不到 会回到一级缓存查找
http://www.lryc.cn/news/468556.html

相关文章:

  • 【计算机网络一】网络学习前置知识
  • nuScenes数据集使用的相机的外参和内参
  • 数据结构与算法:贪心算法与应用场景
  • 音频编解码器音频文件格式
  • FreeSWITCH JSON API
  • 学习docker第三弹------Docker镜像以及推送拉取镜像到阿里云公有仓库和私有仓库
  • 一文掌握Kubernates核心组件,构建智能容器管理集群
  • 正则表达式快速入门
  • 【小程序】-基础语法(二)
  • js 填充数组
  • AI创作3款软件分享,助力内容创作者高效产出优质作品
  • A survey of loss functions for semantic segmentation——论文笔记
  • docker部署es与kibana Mac
  • redis的渐进式哈希?说一下细节?------面试题分享
  • javaWeb项目-springboot+vue-车辆管理系统功能介绍
  • redis和memcached的区别
  • 构建安全基石:网络安全等级保护定级指南
  • PyQt 入门教程(3)基础知识 | 3.1、使用QtDesigner创建.ui文件
  • 解锁金融大门,你的基从备考秘籍全揭秘!
  • 详解Linux系统中的设备驱动程序.ko文件
  • MG协议转换器:高效连接,智控未来
  • pycharm设置自动格式化代码
  • AI应用程序低代码构建平台Langflow
  • QT-使用QSS美化UI界面
  • 【程序员笔记】-- 常用开发工具汇总
  • 基于SSM考研助手系统的设计
  • 【MacOS】RocketMQ 搭建Java客户端
  • 前端学习---(5)js基础--3
  • Spring Boot 3.3.4 升级导致 Logback 之前回滚策略配置不兼容问题解决
  • 如何开发属于自己的Hoobuy跨境独立站