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("下达循环停止指令");
    }

}

6680 次点击
所在节点    Java
37 条回复
vansl
2019-03-01 12:33:51 +08:00
是想验证进行 I/O 等导致线程阻塞的操作时会刷新本地内存?貌似没有意义吧。
momocraft
2019-03-01 13:05:47 +08:00
做了( Jawa 内存模型中能保证内存可见性的事,如 volatile / monitor / explicit lock )就应该看得到

不做未必看不到,但应把看到视作偶然,不要基于巧合编程
gamexg
2019-03-01 14:02:06 +08:00
赞同楼上,不要依赖巧合。
非 java 程序员,不清楚语言有哪些线程同步机制。
但是大部分语言都是一样的,如果代码不明确的线程同步,那么编译器、cpu 有可能做出各种缓存、乱序执行,结果会不可预期。
xomix
2019-03-01 15:16:48 +08:00
虽然主语言不是 java,但是赞同不要基于巧合编程的答案。
peyppicp
2019-03-01 15:30:15 +08:00
keepRunning 这个变量是在内存上的,并不在 cpu 缓存上,读取运算的时候要先从内存加载到缓存上,如果 cpu 的缓存没有更新,那么读到的就是旧值。
IO 线程在遇到阻塞时,cpu 会将其切换,在其重新执行时,会重新从内存中加载数据,如 keepRunning,这个时候 keepRunning 已经被更新了,所以循环就停止了。
codehz
2019-03-01 15:40:51 +08:00
推荐去了解一下 java 的内存模型,这个关键词应该能搜索到相关内容了
gaius
2019-03-01 15:42:16 +08:00
用 volitale
xzg
2019-03-01 15:53:59 +08:00
@peyppicp 赞成楼上的说法,读取 cpu 缓存的值是关键
neoblackcap
2019-03-01 16:40:13 +08:00
@peyppicp JIT 之后 keepRunning 会不会优化成在 CPU 缓存上啊?
reus
2019-03-01 16:46:04 +08:00
最怕拿一次两次的测试当真理
如果内存模型没有保证,那可能下个版本就不是这样了,你这就是埋坑
peyppicp
2019-03-01 16:52:47 +08:00
@neoblackcap 在这个 case 下,keepRunning 应该会被 JIT 优化到 main 函数里面的一个局部变量,这个这个玩意是分配在栈上的,栈在内存上,所以还是会在内存上,并不会优化到 cpu 缓存。

个人想法,欢迎各位探讨
yidinghe
2019-03-01 16:53:23 +08:00
多线程访问同一个对象或变量,要严格进行同步操作。最简单的办法就是 synchronized 关键字。
turnrut
2019-03-01 16:55:49 +08:00
跟 java 内存模型没太大关系, cpu 为了性能会优先从自己的独立高速缓存(程序无法感知)操作数据, intel 的指令里专门提供了一个前缀 F0H 强制使用主内存.
The LOCK prefix (F0H) forces an operation that ensures exclusive use of shared memory in a multiprocessor environment.
详见 Intel® 64 and IA-32 Architectures Software Developer's Manuals Vol. 2A 2.2.1
链接 https://software.intel.com/sites/default/files/managed/39/c5/325462-sdm-vol-1-2abcd-3abcd.pdf
neoblackcap
2019-03-01 17:09:23 +08:00
@peyppicp
@turnrut

感觉还是 @turnrut 说得对啊。不过这样又会牵涉到硬件,毕竟这代码只是 Java。比如这个 JVM 是跑在非 x86 的 CPU 上,那结果大概也会不一样。
turnrut
2019-03-01 17:15:08 +08:00
上面说的有点问题, 专门有几个指令用来刷新 cpu cache 的
比如 CLFLUSH — Flush Cache Line
https://www.felixcloutier.com/x86/clflush
peyppicp
2019-03-01 17:15:38 +08:00
@neoblackcap 是我说的不清楚? cpu 执行指令的时候要先从内存加载到缓存,我已经说明过了。JIT 优化 keepRunning 之后,keepRunning 也会分配到内存上,执行的时候加载到 cpu 缓存里。JIT 是不能直接优化到 cpu 缓存里面的
neoblackcap
2019-03-01 17:29:48 +08:00
@peyppicp 是啊,会加载到缓存。那么程序应该是直接读缓存的吧?那么比如线程 1 在核心 1 上跑,线程 2 在核心 2 上跑,线程 1 将 keepRunning 设成 false,这里没有同步的话,这个 keepRunning 的值按道理不会立刻刷新核心 2 的高速缓存吧。什么时候线程 2 停止应该是不确定的。
gtexpanse
2019-03-01 17:46:17 +08:00
你得到的结论只是巧合——如果严格点从 jvm 的角度来说(其实这个“什么时候刷新”跟 jvm 也没啥关系)。
gamexg
2019-03-01 18:03:52 +08:00
@neoblackcap cpu 高速缓存问题倒是不用担心,cpu 硬件可以保证 cpu 核 1 更新了内存时其他核心的缓存会失效。
gamexg
2019-03-01 18:06:04 +08:00
@gamexg #19 但是这里只是直接读写内存硬件上可以保证高速缓存不会是旧数据。

应用程序自己从内存读到寄存器的数据不会受这个保护,还会是旧值。

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

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

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

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

© 2021 V2EX