讨论下Java中的volatile和JMM(Java Memory Model)Java内存模型

接上两篇《大佬们在说的AQS,到底啥是个AQS(AbstractQueuedSynchronizer)同步队列》和《Java中说的CAS(compare and swap)是个啥》的内容,都提到了volatile,虽然网络上这块已经被各个大佬讲烂了,我也回忆复习一下,并且加入我自己的理解,因为我是自学的Java所以不是专业科班的标准答案,还有我自己的理解在里面,如果有错误欢迎指正。
JMM(Java Memory Model)Java内存模型
在讨论 volatile 之前,我们需要先了解一下JMM(Java Memory Model)Java内存模型,如果没有 JMM 直接讨论 volatile 会有点奇怪,所以还是得先说下 JMM。
为了保证共享内存的正确性(可见性、有序性、原子性),内存模型定义了共享内存系统中多线程程序读写操作行为的规范。JVM 也定义了自己的内存模型,注意!这里的 JMM 内存模型跟 JVM 内存模型,不是一回事,并且也不能做比较,完全是两码事哈,不要记叉了。
简单描述一下,Java 将内存划分为了主内存(main memory)和工作内存(working memory),主内存可以理解为大家一起用的内存,工作内存是每个线程自己的内存,就像 CPU Cache 缓存一样,每个核心有自己的缓存而不是共用 RAM 内存,在程序操作变量的时候是在操作自己的工作内存,然后再写回主内存,所以在多个线程操作同一个变量的时候就可能出现问题,因此JMM需要提供原子性、可见性、有序性的保证。
Java 内存模型和硬件内存架构是不同的。硬件内存架构不区分线程堆栈和堆。在硬件上,线程栈和堆都位于主存中。部分线程堆栈和堆有时可能存在于 CPU 缓存和内部 CPU 寄存器中。
但是,我看网上有人说工作内存(working memory)是在栈上,我有点不同意,我觉得工作内存(working memory)是对 CPU Cache 缓存的抽象,不知道对不对?
原子性
JMM保证除 long 和 double 以外的基本数据类型的读写操作是原子性的,当然你用 synchronized 也可以保证原子性,这依赖 monitorenter 和 monitorexit 监视器指令。
为啥 long 和 double 特殊呢?因为他俩都是 64 位的,要照顾 32位 CPU,就把这种拆成两个 32位来操作,所以JVM规范不保证原子性,但鼓励JVM去实现原子性,我记得加 volatile 好像就是原子的了,不确定,还得查具体的 JVM 实现的文档,这太细节了。
可见性
就是咱们要讨论的 volatile 了,强制变量的赋值会同步刷新回主内存中,强制变量的读取从主内存中加载,保证不同线程始终能看到该变量的最新值,这就保证了可见性。
有序性
也是咱们要讨论的 volatile,可以阻止指令重排,还有 happens-before 原则,后面慢慢展开说,这节是 JMM 内容,就先到这里吧。
Volatile
终于到主题 volatile 了,这货在 JUC 里写的满世界都是,说明很重要啊,了解一下 volatile 是干啥的。
Volatile可见性
在上面 JMM 的介绍中,我们知道程序不是直接在主内存中交互的,而是复制一个副本到自己的工作内存里进行操作,这就会导致线程A在核心1上的操作,线程B在核心2上也操作,但他们相互之间可能看不见,加 volatile 修饰的变量就可以解决这个问题,当修改以后其他线程就可以立即看到修改后的值,怎么做到的呢?先了解一下一致性协议,例如 Intel 的 MESI 协议。
MESI(缓存一致性协议)
当一个CPU修改变量时,发现在其他核心也有这个变量的缓存,会通知其他核心将缓存设置为过期状态,这样其他核心再读取时从主内存中直接读取,而不是自己的缓存中读取。
volatile 这么好为啥不给变量都加上?如果你滥用 volatile 也会造成一些问题,上面我们介绍了缓存一致性协议,CPU 之间的通讯是依赖总线的,总线的带宽就那么多,如果你使用大量 volatile 并且配合 CAS 自旋,会在总线上造成消息风暴,占用总线带宽,那么机器的性能也会下降。
Volatile有序性
volatile 可以禁止指令重排,现在的系统为了提高效率,会重排序咱们的代码指令,其中包括编译期间的重排、CPU执行期间的指令重排,比如上一条指令和下一条指令没啥依赖关系,那么就可能被重排序。
volatile 在编译时会在适当的位置插入内存屏障,有四种屏障:
- StoreStore:插入到两个 Store 中间,就可以确保下面的以及后续的 Store 操作可以看到上面的 Store 操作
- LoadLoad:插入到两个Load操作中间,就可以确保下面的 Load 读取可以读取到上面 Load 的数据
- LoadStore:插入 Load 与 Store 中间,就可以确保下面的 Store 执行前可以读取到上面的 Load 操作
- StoreLoad:插入 Store 与 Load 中间,就可以确保下面的 Load 执行前可以读取到上面的 Store 操作
有点复杂,从JDK5开始,提出了happens-before的概念,通过这个概念来阐述操作之间的内存可见性。
有了这个概念,就可以描述为:对一个volatile域的写操作,happens-before于任意线程后续对这个volatile域的读,再大白话一些,你改了volatile域的变量,那么后面任意线程都可以读得到。
Volatile无法保证原子性
前面说的可见性好像很好使,但直接操作 volatile 变量其实无法保证原子性,你虽然可见它的变化,但是通过内存屏障保证了happens-before,让单次读写变得有序,只能保证你读取到最新的,你读取成功以后,做操作的时候,其他线程可能还在修改,所以无法标准原子性。
volatile与synchronized
很多人说 volatile 是轻量级的 synchronized,volatile 只保证了可见性的读取,但涉及到写入的时候,你无法阻止其他线程也在修改。
商业用途请联系作者获得授权。
版权声明:本文为博主「任霏」原创文章,遵循 CC BY-NC-SA 4.0 版权协议,转载请附上原文出处链接及本声明。
相关推荐
猜你还喜欢这些内容,不妨试试阅读一下评论与留言
以下内容均由网友提交发布,版权与真实性无法查证,请自行辨别。微信订阅号
扫码关注「任霏博客」微信订阅号- 你好,我想问一下如果是分析型的数据库要怎么制作docker镜像呢 是修改V008R003C002B0320版本号吗
- 可以的,我也正在开发分享的程序,可以邮件或群联系我都可以,关于页面里有联系方式:https://www.renfei.net/page/about 。
- 有破解软件的需要可以私下联系您吗?
- 您好,手机APP只是个客户端,用于数据呈现展示,数据均保存在服务器上,只留个APP没有任何用处,无能为力哦。
- 老哥 看你弄了这么多软件好厉害啊。 我有个软件 我买过几个小会员 没用几天 然后商家跑路了,软件服务器关闭了,连不上去 用不了。 你能做成一个打补丁版本可以本地用的么? 方便看下么?https://haodezhe.lanzouw.com/iD0f30h9joza 谢谢老哥!
- 您好,由于版权投诉和我国知识产权法的完善,我已经下架所有破解软件的下载链接了。
- 请问怎么下载呀
- 我保存的License在:https://gitlab.com/renfei/KingbaseES-V8-R3/-/tree/master/License ,开发版是长期有效的,只不过限制连接数,现在官网好像已经下线 V8R3 的下载页面了,其他版本我也不确定是否过期
- 这个版本的license有没有
- 序列号长度不对呀
- 优雅的源代码管理(二):Git 的工作原理
- 优雅的源代码管理(一):版本控制系统 VCS(Version Control System)与软件配置管理 SCM(Software Configuration Management)
- ChatGPT 开发商 OpenAI 买下极品域名 AI.com
- 火爆的 AI 人工智能 ChatGPT 国内注册教程、使用方式和收费标准
- 解决 SpringCloud 中 bootstrap.yml 不识别 @[email protected] 参数
- Cron表达式书写教程搞定Linux、Spring、Quartz的定时任务
- 阿里云香港可用区C发生史诗级故障
- 国产统信UOS服务器操作系统V20提供免费使用授权
- 开源站长推送工具效果评测推荐(百度/必应/谷歌)
- 获取公网IP服务「ip.renfei.net」升级增加地理定位数据字段公示