讨论下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 版权协议,转载请附上原文出处链接及本声明。
相关推荐
猜你还喜欢这些内容,不妨试试阅读一下评论与留言
以下内容均由网友提交发布,版权与真实性无法查证,请自行辨别。微信订阅号
扫码关注「任霏博客」微信订阅号- 大佬 引入jar包那里的 driver class 怎么选的?
- 我也遇到了这个问题,已经解决了,在此分享一下 1、宿主机也要创建kingbase的用户和用户组,并且要查看一下用户和用户组的ID(这个很重要) 2、把data目录的用户和用户组设置为kingbase 3、先不要把data路径挂载到宿主机上,这时就可以正常启动,启动后进入容器,查看一下容器内的kingbase的用户和用户组ID是多少,和第一步的ID是否一致,如果ID一致,那正常挂载目录就行;如果ID不一致,那就需要修改Dockerfile文件,在构建镜像时,修改容器内的用户和用户组ID,必须和宿主机的保持一致。然后重新构建镜像,就可以正常挂载宿主机目录了 4、其实直接修改宿主机的用户和用户组ID也是可以的,但是容器内的ID一般是1000,但是宿主机的这个ID很可能已经被占用了,无法修改,就只能修改容器内的ID
- 接口已经允许跨域请求,也就是说你可以在你的页面上调用,获取用户的公网 IP。 如果你还需要其他需求,可以提交 Issue 给我。
- V008R003C002B0320 这个对应的jdbc链接驱动你在哪里找到的?我也遇到了这个问题。
- WARNING: max_connections should be less than orequal than 10 (restricted by license) HINT: the value of max_connect is set 10 WARNING: max_connections should be less than orequal than 10 (restricted by license) HINT: the value of max_connect is set 10 kingbase: superuser_reserved_connections must be less than max_connections 我按照文档修改了以后,不知道如何重启。
- 然后把数字都改成 1 再启动。 如何重新启动?
- ksql: could not connect to server: No such file or directory Is the server running locally and accepting connections on Unix domain socket "/tmp/.s.KINGBASE.54321"
- 进入容器查看一下日志,是不是启动失败了,日志文件在:/opt/kingbase/logfile
- ksql: could not connect to server: No such file or directory Is the server running locally and accepting connections on Unix domain socket "/tmp/.s.KINGBASE.54321"?
- 先通过 docker exec -it 容器名/id /bin/bash 进入容器,然后在容器中使用 ksql 客户端进行连接数据库:/opt/kingbase/Server/bin/ksql -U system test
- 免费.ml域名10年委托合同到期被马里共和国收回域名经营权
- 从极狐Gitlab看各种中间件技术选型
- 时隔十年首次收到 Google AdSense 的付款
- ga域名被加蓬共和国从Freenom公司手中收回域名经营权
- Freenom 被 Meta(Facebook) 起诉导致暂停 .tk/.ga/.ml/.cf/.gq 等新域名注册
- 生花妙笔信手来 – 基于 Amazon SageMaker 使用 Grounded-SAM 加速电商广告素材生成 [1]
- github.renfei.net 不再完整代理 Github 页面改为代理指定文件
- 优雅的源代码管理(三):本地优雅的使用 Git Rebase 变基
- 优雅的源代码管理(二):Git 的工作原理
- 优雅的源代码管理(一):版本控制系统 VCS(Version Control System)与软件配置管理 SCM(Software Configuration Management)