18 | Java内存模型:Java中的volatile有什么用?
海纳
你好,我是海纳。
随着这节课的开始,我们将进入到专栏的最后一个模块:自动内存管理篇。在这个模块,你将会了解到,以 Java 为代表的托管型语言是如何自动进行内存管理和回收整理的,这将提高你使用 Java、Python、 Go 等托管型语言的能力。
为什么我要把自动内存管理篇放到最后才讲呢?因为要理解这一篇的内容,需要软件篇和硬件篇的知识做铺垫。比如说,在面试时,有一个问题面试官问到的频率非常高,但几乎没有人能回答正确,因为它需要的前置知识太多。这个问题是:Java 中的 volatile 有什么用?如何正确地使用它?
这个问题之所以会频繁出现在面试中,是因为 Java 并发库中大量使用了 volatile 变量,在 JVM 的研发历史上,它在各种不同的体系结构下产生了很多典型的问题。那么,在开发并发程序的时候,深刻地理解它的作用是非常有必要的。
幸运的是,前面硬件篇的知识已经帮我们打好了足够的基础,今天我们就可以深入讨论这个问题了。由于在这个问题中,volatile 的语义是由 Java 内存模型定义的,我们就先从 Java 内存模型这个话题聊起。
Java 内存模型
我们知道在不同的架构上,缓存一致性问题是不同的,例如 x86 采用了 TSO 模型,它的写后写(StoreStore)和读后读(LoadLoad)完全不需要软件程序员操心,但是 Arm 的弱内存模型就要求我们自己在合适的位置添加 StoreStore barrier 和 LoadLoad barrier。例如下面这个例子:
公开
同步至部落
取消
完成
0/2000
荧光笔
直线
曲线
笔记
复制
AI
- 深入了解
- 翻译
- 解释
- 总结
本文深入探讨了Java内存模型(Java Memory Model,JMM)的重要性和作用,从硬件和软件角度出发,介绍了不同架构下的缓存一致性问题,以及JMM的Happens-before模型。强调了同步动作的先后关系,并通过控制流依赖和数据流依赖的例子阐述了Happens-before模型的理论性和实用性。文章指出JMM需要结合具体的CPU体系结构来理解,并提到了Doug Lea对JMM的简化描述,使读者更容易理解JMM的概念。此外,还举例说明了JMM在实际场景中的应用,如AQS的内部实现和线程安全的单例模式。通过深入讨论JMM的理论基础和实际应用,帮助读者更好地理解并发编程中volatile的作用和正确使用方法。文章内容丰富,涵盖了JMM的理论和实践,对于想深入了解并发编程的读者具有重要参考价值。
仅可试看部分内容,如需阅读全部内容,请付费购买文章所属专栏
《编程高手必学的内存知识》,新⼈⾸单¥59
《编程高手必学的内存知识》,新⼈⾸单¥59
立即购买
© 版权归极客邦科技所有,未经许可不得传播售卖。 页面已增加防盗追踪,如有侵权极客邦将依法追究其法律责任。
登录 后留言
全部留言(13)
- 最新
- 精选
- 大豆1、volatile不能代替锁,它的作用是绕过高速缓存,直接与内存进行交互并读写数据。 2、当多线程时,会存在线程A将新值写入内存前,线程B又从内存读取了旧值,这样就会导致sum的值不会是80000。 3、要想sum的值为80000,还得给Main.sum += 1;这句代码加锁。 4、volatile还有一个作用是防止指令重排,对吗?老师。
作者回复: 太对了,同学!
2021-12-0868 - AriseFX请教一个问题,思考题如果使用CAS来更新sum的话,这里使用volatile关键字还有意义么,CAS不是已经包含了volatile读/写的语义么?
作者回复: 使用cas,就不用再使用volatile了,cas确实会保证可见性。
2022-02-1653 - 王建新菜鸡求请教:acquire 语意,和release 语意 都代表什么,求解答。。
作者回复: 第15课里有的,volatile之所以难就是需要的前置知识多。要耐心点从头看。
2021-12-152 - .volatile作用的自我总结: 1.给编译器看在编译层面禁止重拍 2.给虚拟机看让其在对应的指令加入屏障。防止cpu级别的重排序与缓存一致性问题 思考题: 这个问题就像++i问题类似,由于加一是由多个字节码才能完成(java虚拟机基于栈的设计尤其明显比安卓虚拟机完成一件事需要更多指令)。假设cpu1取出数据放入操作栈,还没进行计算操作,而此时时间片正好用完另线程cpu2完成一次加一,在切回cpu1时数据已经错误。 解决方案: 1.加锁保证多线程的串行 2.操作变成原子操作,如使用并发包下的原子类 老师能不能以后讲讲cpu之上的内存模型呢?比如arm
作者回复: 总结得非常好!15,16课已经讲过了啊,arm上就是采用了dmb, arquire, release这三种。
2021-12-081 - 送过快递的码农我尝试对之前的知识和今天的文章我来梳理一下。volatile 关键字修饰的变量要遵循happen-bofore模型。由于cpu缓存的情况,我们会出现读滞后数据的情况。前面的知识,cpu通过缓存一致性协议,对缓存状态进行管理,一旦失效,会通过总线同步给其他核心。但是由于,这样性能成本太高,所以出现写缓存区+失效队列+内存屏障来进行补充?又由于我理解这个是jmm内存模型提出的规范,由于不同平台,不同cpu缓存架构不一样,cpu所提供的内存屏障实现也不一样。volatile关键字,虽说是可见性,但是也是集cpu,操作系统,jvm这些在不同层级上做自己的事情,方能实现。感觉也是现代计算机知识体系的一个精华(由于,前面很多看了不懂不懂,所以麻烦老师看看我的理解对不对,感觉这个也是要多刷的,不然越学越废)
作者回复: 我觉得你这个总结还是感性上的一种总结,还不够精细。大体方向是对的。但细节还要再多抠一下。
2021-12-092 - raisecomer“happen-before模型”的表格中“对volatile的写操作在对该变量的读操作之前执行”,太令人费解了
作者回复: 这就是jsr133文档难受的地方,我这里是照着翻译回来的。其实他想说的是写操作如果在程序里出现在读操作之前,那就不能乱序。这就是写后读屏障的规则。其实你不用太在意jsr133说的是什么,只要看Doug lea给的那张表就行了。
2021-12-082 - 李二木volatile 只能保证可见性和有序性。不能保证原子性。
作者回复: Bingo~
2021-12-08 - anqivolatile 可以保证可见性,但无法保证原子性 因为数据可能在 寄存器或者 (store buffer + cpu cache + memory)组成的内存子系统中, 当在寄存器中时,如果发生中断,另一个线程仍然可以在上面的内存子系统中读到同样的值, 造成两次操作都是基于一个值做了自增操作,虽然刷新回内存的操作可以保证可见性,但已经于事无补了。2022-05-302
- AriseFX思考题 public void run() { long sumOffset = UNSAFE.staticFieldOffset(Main.class.getDeclaredField("sum")); for (int i = 0; i < 40000; i++) { UNSAFE.getAndAddInt(Main.class, sumOffset, 1); } }2022-02-161
- 卖藥郎volatile 能替代锁(或者 CAS 操作)的能力吗? 答:不能。比如 i++,实际会包含读改写三个操作,T1从主存中读取值之后,由于没有原子性限制,主存中的值可能会在此刻发生变化。 不知道这样理解的对不对,望老师指正2022-03-31
收起评论