Java 多线程并发,线程什么时候会刷新 "工作内存"

2019-03-01 12:23:19 +08:00
 laoluo1991

讨论一个 Java 多线程相关的问题

存在线程 A、B, 共享变量 v , B 会循环读取变量 v

线程 A 对共享变量 v 进行了修改

线程 B 什么情况下能读到线程 A 修改后的值

测试代码如下

目前已知测试
循环体内为空的时候不停
循环体内为 Object obj = new Object(); 的时候不停
循环体内为 System.out.println(); 的时候停
循环体内为 Thread.sleep(1000); 的时候停
循环体内为 File file = new File("C:\work\test.txt"); 的时候停
public class TestDemo {

    private  static boolean  keepRunning = true;

    public static void main(String[] args)  throws Exception {
        new Thread(
            ()->{
                while (keepRunning){
                    //do something
                }
                System.out.println("循环停止");
            }
        ).start();
        Thread.sleep(1000);
        keepRunning = false;
        System.out.println("下达循环停止指令");
    }

}

6702 次点击
所在节点    Java
37 条回复
letianqiu
2019-03-01 18:06:11 +08:00
@peyppicp 你是基于发生 context switch 的时候 CPU 会 flush 掉 cache,这个不一定成立。
zjp
2019-03-01 18:11:46 +08:00
System.out.println();方法有 synchronized 修饰,使得虚拟机很有可能刷新本地内存。然后有些错误的并发代码加了行输出做调试就看起来正常了……
neoblackcap
2019-03-01 18:16:58 +08:00
@gamexg 我记得哪怕 x86 也要加对应的内存屏障啊
gamexg
2019-03-01 18:25:06 +08:00
@neoblackcap #23 关键字 高速缓存一致性
Banxiaozhuan
2019-03-01 18:41:44 +08:00
@neoblackcap 我咋感觉这些回复都很水。。。。
都撤到了系统架构。。。 傻不傻,, 看看七楼回答的这个 volitale。
人家都帮你做好了,还在乱研究,多读书,别做无头苍蝇。
fuyufjh
2019-03-01 19:07:58 +08:00
memory barrier

ps. JVM 内存模型就像 java 标准一样,是给 JVM 开发者看的。各位 Java 用户直接去搞懂 CPU cache 就足够了
choice4
2019-03-01 19:11:56 +08:00
为了提升性能,线程里面有工作内存,这样访问数据不用去主存读取,可以快一些。共享变量被线程修改后,该线程的工作内存中的值就会和其他线程不一致,也和主存的值不一致,所以需要将工作内存的值刷入主存,但是这个刷入可能其他线程并没有看到。使用 volatile 后可以通过 cpu 指令屏障强制要求读操作发生在写操作之后,并且其他线程在读取该共享变量时,需要先清理自己的工作内存的该值,转而重新从主存读取,volatile 保证一定会刷新,但是不写也不一定其他线程看不见。
就是上面大哥说的巧合(即这种不一定每次都会有正确的保障)
HhZzXx
2019-03-01 19:22:14 +08:00
推荐看 java concurrent in practice
asd123456cxz
2019-03-01 19:58:48 +08:00
同意解决问题的方式使用 volatile,这是字节码指令->内存屏障的事。至于(无同步操作下,何时工作内存数据刷到主存)这个问题我也想过,如果有大神了解希望解答。
cyspy
2019-03-01 20:02:21 +08:00
如果不加 volatile,应该是写入方的 cache 被刷新到主存之后,读取方 cache 失效的时候,从主存里取到新值
turnrut
2019-03-01 21:13:44 +08:00
@asd123456cxz 抛开硬件中断的情况,cpu 顺序执行内存里的指令,假设它的高速缓存是 1k,当它开始执行 3k 位置处的指令,写回原缓存,并把 3-4k 的数据度入缓存里,在执行出这个范围外前一定会写回内存。至于在这个缓存范围内循环执行,不保证是否写回和写回的频率。
再来谈中断的情况,中断后会去执行预设固定位置的代码,简单的把它看成一次大跳转,中断前后一定会刷新缓存。然后系统内核提供给用户空间的接口都是(软)中断实现的,比如读取一个文件。即使不用内核的中断写一个死循环,但是还有最基础的硬件时间中断,比如进程和线程的调度就靠它。
这个问题分成两层,如果想写正确的 java 代码,那只需要清楚 java 里几个关键字的语义。原理的话,天然离不开 cpu 和操作系统这些底层的东西,每一层抽象都为下一层提供语义上的保证,代码最终还是老老实实的跑在硬件上。
yuyujulin
2019-03-01 22:22:27 +08:00
@choice4 这个主内存是不是就是 CPU 的缓存呢?
choice4
2019-03-01 23:23:43 +08:00
@yuyujulin java 里边可以简单理解为 jvm 堆区,
asd123456cxz
2019-03-02 20:26:14 +08:00
@turnrut #31 感谢大佬。话说出于好奇看了下你之前的回复。。感觉好强啊,同样是自学 Java 差距巨大,不介意的话可以加个微信交流下吗?我的微信是 sul609。或者讲讲大佬你的学习路线学习途经什么的也是极好的!
yuyujulin
2019-03-02 21:04:25 +08:00
@choice4 那这么说的话,跟 CPU 缓存是没什么关系咯?
choice4
2019-03-02 21:39:09 +08:00
@yuyujulin 主存主要包括本地方法区和堆区,线程工作内存主要包括 线程私有的栈区和对主存中部分变量拷贝的寄存器(包括程序计数器和 cpu 高速缓存)
lswang
2020-10-16 14:56:55 +08:00
楼主应该是不明白 while 循环里面是空的时候,为何循环停不了吧。(因为一开始我也想不明白为什么)
停不了的原因是 JIT 作祟了。楼主可以在 java 运行参数中加上 -Xint,while 里面即使为空,也是可以结束的

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/539969

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX