首页 > 其他分享 >Volatile的底层原理

Volatile的底层原理

时间:2024-08-22 18:53:06浏览次数:8  
标签:volatile 变量 处理器 缓存 线程 内存 Volatile 原理 底层

Volatile的底层原理

volatile的特点

被volatile修饰的变量具有如下特点:

  • 1.保证此变量对所有的线程的可见性,不能保证它具有原子性(可见性,是指线程之间的可见性,一个线程修改的状态对另一个线程是可见的)

  • 2.禁止指令重排序优化

  • 3.volatile 的读性能消耗与普通变量几乎相同,但是写操作稍慢,因为它需要在本地代码中插入许多内存屏障指令来保证处理器不发生乱序执行

通过在编译期,给目标变量添加ACC_VOLATILE标识,最终在cpu指令前形成lock前缀。

  • 线程对变量赋值后,都会立即刷新到主存,并且通知其他线程其工作内存中的缓存失效,再次访问必须从主存加载。

  • 编译期、执行期形成内存屏障,变量的读、写前的逻辑,不能重排到屏障后。

JMM内存模型

Java内存模式是一种虚拟机规范,它用于屏蔽掉各种硬件和操作系统的内存访问差异,以实现让Java程序在各种平台下都能达到一致的并发效果。

JMM规范了Java虚拟机与计算机内存是如何协同工作的:规定了一个线程如何和何时可以看到由其他线程修改过后的共享变量的值,以及在需要时如何同步的访问共享变量。

JMM内存模型把虚拟机内存分为主内存和工作内存

主内存

主内存是处理器共享的存储空间。主内存用于存储共享变量,是多个线程之间共享的数据存储区域。多个线程可以同时访问主内存中的共享变量,但是线程对共享变量的修改不一定会立即同步到主内存中。

Java内存模型规定:线程对共享变量的修改操作必须先在线程自己的工作内存中进行,然后可能会被延迟到主内存中去

工作内存

每个线程都有一个私有的工作内存。工作内存是 JMM 的一个抽象概念,并不真实存在。工作内存是寄存器和高速缓存的抽象。我们可以约等于理解为工作内存即为cpu的寄存器或者高速缓存。线程执行的时候,首先从主内存读值,再保存为工作内存中的副本,然后交给cpu执行,执行完毕后再给副本赋值,随后工作内存再把值传回给主存。

主内存和工作内存间的交互

JMM中定义了如下8种操作,来完成主内存和工作内存的数据交互:

  • Lock(锁定):作用于主内存中的变量,表示变量被一个线程独占的状态。

  • Unlock(解锁):作用于主内存中的变量,将变量从锁定状态(Lock)释放,释放后的变量可被其他线程锁定(Lock)

  • Read(读取):作用于主内存中的变量,将变量从主内存传输到工作内存中的过程

  • Load(加载):作用于工作内存中的变量,把read操作从主内存中得到的变量的值放入工作内存的变量副本中。

  • Use(使用):作用于工作内存中的变量,把工作内存中变量传递给执行引擎用于计算等等。(可理解为使用这个变量)

  • Assign(赋值):作用于工作内存中的变量,把一个从执行引擎接收到的值赋值给工作内存中的变量。(可理解为给变量赋值。)

  • Store(存储):作用于工作内存中的变量,把工作内存中的一个变量传送到主内存中,以便随后write 操作使用。

  • Write(写入):作用于主内存中的变量,把store操作从工作内存中得到的变量的值放入主内存的变量中。

需要注意一下几点:

  1. read和load ;store和write 是成对出现的,才能实现数据完整的拷贝
  2. 执行了assign(赋值),就必须执行store和write 把更改同步到主内存
  3. use前必须执行load,load前必须执行read
  4. lock只允许一个线程同时执行
  5. lock一个变量的时候,工作内存中的此变量的值将会清空,所以use(使用)前必须重新read(读取)和load(加载)初始化变量的值。
  6. unlock前必须把变量刷会主内存(即store和write)

一、可见性问题

volatile修饰的变量如何能刷回主内存

通过对OpenJDK中的unsafe.cpp源码的分析,会发现被volatile关键字修饰的变量会存在一个“lock:”的前缀。

Lock前缀,Lock不是一种内存屏障,但是它能完成类似内存屏障的功能。Lock会对CPU总线和高速缓存加锁,可以理解为CPU指令级的一种锁。当CPU发现这个指令时,立即会做两件事情

1.会将当前处理器缓存行的数据直接写回到系统内存中

2.这个写回内存的操作会使在其他CPU里缓存了该地址的数据无效。这是通过cpu总线缓存一致协议来保证的

缓存一致性协议

每个处理器通过嗅探在总线上传播的数据来检查自己缓存的值是不是过期了,当处理器发现自己缓存行对应的内存地址被修改,就会将当前处理器的缓存行设置成无效状态,当处理器要对这个数据进行修改操作的时候,会强制重新从系统内存里把数据读到处理器缓存里。

所以,如果一个变量被volatile所修饰的话,在每次数据变化之后,其值都会被强制刷入主存。而其他处理器的缓存由于遵守了缓存一致性协议,也会把这个变量的值从主存加载到自己的缓存中。这就保证了一个volatile在并发编程中,其值在多个缓存中是可见的。

二、重排序问题

在执行程序时为了提高性能,编译器和处理器常常会对指令做重排序。从java代码一直到最后指令被执行,有多个步骤下会重排指令。

编译器重排序

编译器在编译的时候对没有数据依赖性的操作可以重排序,重排序后不会影响程序语意

由于编译器重的原因,在并发编程的时候就会出现意想不到的问题。

指令集重排序

处理器改变对应机器语言的执行顺序,重排后不会影响程序语意。

这是一个典型的获取单例的例子,但是这样写是有问题的。如果线程A和线程B同时调用getInstance来获取单例,可能其中一个线程调用tools的时候会报空指针异常。为什么呢?

可能会有读者提问,不是判断了tools == null了吗?为什么还会报空指针异常。

如果了解字节码指令的读者会知道对应一个new关键字的时候,处理字节码的常规顺序是

1.内存分配
2.初始化内存实例
3.引用指向内存实例
这里的2和3 的顺序可能被重排,所以当引用指向这块内存的时候内存其实可能没有初始化,如果使用这个引用可能报空指针异常。

内存系统重排序

处理器高速缓存的数据在刷会主内存的时候可能会乱序

volatile是怎么处理重排序问题的

volatile通过 “内存屏障” 的方式来防止指令被重排,为了实现volatile的内存语义,编译器在生成字节码时,会在指令序列中插入内存屏障来禁止特定类型的处理器重排序。大多数的处理器都支持内存屏障的指令。

下面是基于保守策略的JMM内存屏障插入策略:

在每个volatile写操作的前面插入一个StoreStore屏障,禁止上面的普通写和他重排在每个volatile写操作的后面插入一个StoreLoad屏障,禁止跟下面的volatile读/写重排在每个volatile读操作的后面插入一个LoadLoad屏障,禁止下面的普通读和voaltile读重排在每个volatile读操作的后面插入一个LoadStore屏障,禁止下面的普通写和volatile读重排

标签:volatile,变量,处理器,缓存,线程,内存,Volatile,原理,底层
From: https://www.cnblogs.com/JaxYoun/p/18374523

相关文章

  • 分布式事务的Seata AT模式原理
    Seata官网地址:https://seata.apache.org/zh-cn/AT模式优点:无侵入式代码,只需要添加注解,底层采用Seata代理的数据源DataSourceProxy缺点:依赖于数据库,目前只适用于postgresql、oracle、mysql、polardb-x、sqlserver、达梦数据库等数据库,比如业务逻辑中含有redis、es等操作需要控......
  • 电化学一氧化碳传感器:工作原理与应用详解
    电化学一氧化碳传感器是一种用于检测空气中一氧化碳(CO)浓度的设备,它基于电化学原理运作。这类传感器的核心组成部分包括电极、电解质和透气膜,它们共同构成一个微型的电化学电池。电化学一氧化碳传感器的特点包括:快速响应:传感器能够迅速地对气体浓度变化做出反应。高灵敏度:......
  • Prometheus 告警原理详解
    通俗易懂的一篇文章,主要介绍了Prometheus什么时候告警,什么时候不会告警。同时介绍了Prometheus告警原理。警报是监控系统中必不可少的一块,当然了,也是最难搞的一块.我们乍一想,警报似乎很简单一件事:假如发生了异常情况,发送或邮件/消息通知给某人或某频道。 一把......
  • 高性能无锁队列 Disruptor 核心原理分析及其在i主题业务中的应用
    小结:生产者生产数据时,需要入队。消费者消费数据时,需要出队。入队时,不能覆盖没有消费的元素。出队时,不能读取没有写入的元素。因此,Disruptor中需要维护一个入队索引(生产者数据生产到哪里,对应AbstractSequencer中的cursor)和一个出队索引(所有消费者中消费进度最小的序号)。 ......
  • 2024年关于短信轰炸机原理研究并实现的流程 (290021243
    偶然看到gitHub上面有短信轰炸机源码,于是产生了研究的想法。经过研究源码发现是通过抓包进行第三方网站抓包并且收集,最终进行post/get请求。携带header和token进行第三方网站模拟请求。于是在mac上面下载了fiddler进行代理配置开放了本机ip下的8888商品,用手机同时访问本机ip的......
  • docker涉及到的一些原理
    本长文主要和namespace、cgroup、rootfs、unionfs和容器网络有关,仅做学习时的记录,以便之后回顾。参考:https://www.lixueduan.com/categories/docker/page/2/目录深入理解Docker核心原理:Namespace、Cgroups和Rootfs1.基于namespace的视图隔离2.基于Cgroups的资源限制例子:限......
  • 【花雕动手做】腿机构十一种:盘点机器人行走背后的连杆机械原理
    机器人概念已经红红火火好多年了,目前确实有不少公司已经研制出了性能非常优越的机器人产品,我们比较熟悉的可能就是之前波士顿动力的“大狗”和会空翻的机器人了,还有国产宇树科技的机器狗等,这些机器人动作那么敏捷,背后到底隐藏了什么高科技呢,控制技术太过复杂,一般不太容易了......
  • Java线程池实现原理及在美团业务中的实践
    Java线程池实现原理及在美团业务中的实践随着计算机行业的飞速发展,摩尔定律逐渐失效,多核CPU成为主流。使用多线程并行计算逐渐成为开发人员提升服务器性能的基本武器。J.U.C提供的线程池:ThreadPoolExecutor类,帮助开发人员管理线程并方便地执行并行任务。了解并合理使用线程池,是一......
  • 智商测试原理探微:心理学、统计学与测试科学的交融
    简介智力测试就是对智力的科学测试,它主要测验一个人的思维能力、学习能力和适应环境的能力。现代心理学界对智力有不同的看法。所谓智力就是指人类学习和适应环境的能力。智力包括观察能力、记忆能力、想象能力、思维能力等等。智商测试的原理心理学理论:智商测试的设计和......
  • LLM | 一文带你揭秘大语言模型ChatGPT的原理
    本文包含大量AI干货预计阅读时间:10分钟本文学习目标:定义语言模型和大型语言模型(LLM)。介绍关键的LLM概念,包括TransFormer和自注意力机制。介绍LLM提示工程、微调和Rag,以及当今热门的大语言模型应用。前言在当今的科技时代,大型语言模型(LLM)正以惊人的速度发展并......