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("下达循环停止指令");
}
}
1
vansl 2019-03-01 12:33:51 +08:00
是想验证进行 I/O 等导致线程阻塞的操作时会刷新本地内存?貌似没有意义吧。
|
2
momocraft 2019-03-01 13:05:47 +08:00 2
做了( Jawa 内存模型中能保证内存可见性的事,如 volatile / monitor / explicit lock )就应该看得到
不做未必看不到,但应把看到视作偶然,不要基于巧合编程 |
3
gamexg 2019-03-01 14:02:06 +08:00 2
赞同楼上,不要依赖巧合。
非 java 程序员,不清楚语言有哪些线程同步机制。 但是大部分语言都是一样的,如果代码不明确的线程同步,那么编译器、cpu 有可能做出各种缓存、乱序执行,结果会不可预期。 |
4
xomix 2019-03-01 15:16:48 +08:00
虽然主语言不是 java,但是赞同不要基于巧合编程的答案。
|
5
peyppicp 2019-03-01 15:30:15 +08:00
keepRunning 这个变量是在内存上的,并不在 cpu 缓存上,读取运算的时候要先从内存加载到缓存上,如果 cpu 的缓存没有更新,那么读到的就是旧值。
IO 线程在遇到阻塞时,cpu 会将其切换,在其重新执行时,会重新从内存中加载数据,如 keepRunning,这个时候 keepRunning 已经被更新了,所以循环就停止了。 |
6
codehz 2019-03-01 15:40:51 +08:00 via Android
推荐去了解一下 java 的内存模型,这个关键词应该能搜索到相关内容了
|
7
gaius 2019-03-01 15:42:16 +08:00
用 volitale
|
9
neoblackcap 2019-03-01 16:40:13 +08:00
@peyppicp JIT 之后 keepRunning 会不会优化成在 CPU 缓存上啊?
|
10
reus 2019-03-01 16:46:04 +08:00
最怕拿一次两次的测试当真理
如果内存模型没有保证,那可能下个版本就不是这样了,你这就是埋坑 |
11
peyppicp 2019-03-01 16:52:47 +08:00
@neoblackcap 在这个 case 下,keepRunning 应该会被 JIT 优化到 main 函数里面的一个局部变量,这个这个玩意是分配在栈上的,栈在内存上,所以还是会在内存上,并不会优化到 cpu 缓存。
个人想法,欢迎各位探讨 |
12
yidinghe 2019-03-01 16:53:23 +08:00
多线程访问同一个对象或变量,要严格进行同步操作。最简单的办法就是 synchronized 关键字。
|
13
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 |
14
neoblackcap 2019-03-01 17:09:23 +08:00
|
15
turnrut 2019-03-01 17:15:08 +08:00
上面说的有点问题, 专门有几个指令用来刷新 cpu cache 的
比如 CLFLUSH — Flush Cache Line https://www.felixcloutier.com/x86/clflush |
16
peyppicp 2019-03-01 17:15:38 +08:00 1
@neoblackcap 是我说的不清楚? cpu 执行指令的时候要先从内存加载到缓存,我已经说明过了。JIT 优化 keepRunning 之后,keepRunning 也会分配到内存上,执行的时候加载到 cpu 缓存里。JIT 是不能直接优化到 cpu 缓存里面的
|
17
neoblackcap 2019-03-01 17:29:48 +08:00
@peyppicp 是啊,会加载到缓存。那么程序应该是直接读缓存的吧?那么比如线程 1 在核心 1 上跑,线程 2 在核心 2 上跑,线程 1 将 keepRunning 设成 false,这里没有同步的话,这个 keepRunning 的值按道理不会立刻刷新核心 2 的高速缓存吧。什么时候线程 2 停止应该是不确定的。
|
18
gtexpanse 2019-03-01 17:46:17 +08:00
你得到的结论只是巧合——如果严格点从 jvm 的角度来说(其实这个“什么时候刷新”跟 jvm 也没啥关系)。
|
19
gamexg 2019-03-01 18:03:52 +08:00
@neoblackcap cpu 高速缓存问题倒是不用担心,cpu 硬件可以保证 cpu 核 1 更新了内存时其他核心的缓存会失效。
|
20
gamexg 2019-03-01 18:06:04 +08:00
|
21
letianqiu 2019-03-01 18:06:11 +08:00
@peyppicp 你是基于发生 context switch 的时候 CPU 会 flush 掉 cache,这个不一定成立。
|
22
zjp 2019-03-01 18:11:46 +08:00 via iPhone
System.out.println();方法有 synchronized 修饰,使得虚拟机很有可能刷新本地内存。然后有些错误的并发代码加了行输出做调试就看起来正常了……
|
23
neoblackcap 2019-03-01 18:16:58 +08:00 via iPhone
@gamexg 我记得哪怕 x86 也要加对应的内存屏障啊
|
24
gamexg 2019-03-01 18:25:06 +08:00 1
@neoblackcap #23 关键字 高速缓存一致性
|
25
Banxiaozhuan 2019-03-01 18:41:44 +08:00
|
26
fuyufjh 2019-03-01 19:07:58 +08:00
memory barrier
ps. JVM 内存模型就像 java 标准一样,是给 JVM 开发者看的。各位 Java 用户直接去搞懂 CPU cache 就足够了 |
27
choice4 2019-03-01 19:11:56 +08:00 via Android
为了提升性能,线程里面有工作内存,这样访问数据不用去主存读取,可以快一些。共享变量被线程修改后,该线程的工作内存中的值就会和其他线程不一致,也和主存的值不一致,所以需要将工作内存的值刷入主存,但是这个刷入可能其他线程并没有看到。使用 volatile 后可以通过 cpu 指令屏障强制要求读操作发生在写操作之后,并且其他线程在读取该共享变量时,需要先清理自己的工作内存的该值,转而重新从主存读取,volatile 保证一定会刷新,但是不写也不一定其他线程看不见。
就是上面大哥说的巧合(即这种不一定每次都会有正确的保障) |
28
HhZzXx 2019-03-01 19:22:14 +08:00
推荐看 java concurrent in practice
|
29
asd123456cxz 2019-03-01 19:58:48 +08:00
同意解决问题的方式使用 volatile,这是字节码指令->内存屏障的事。至于(无同步操作下,何时工作内存数据刷到主存)这个问题我也想过,如果有大神了解希望解答。
|
30
cyspy 2019-03-01 20:02:21 +08:00
如果不加 volatile,应该是写入方的 cache 被刷新到主存之后,读取方 cache 失效的时候,从主存里取到新值
|
31
turnrut 2019-03-01 21:13:44 +08:00 via Android
@asd123456cxz 抛开硬件中断的情况,cpu 顺序执行内存里的指令,假设它的高速缓存是 1k,当它开始执行 3k 位置处的指令,写回原缓存,并把 3-4k 的数据度入缓存里,在执行出这个范围外前一定会写回内存。至于在这个缓存范围内循环执行,不保证是否写回和写回的频率。
再来谈中断的情况,中断后会去执行预设固定位置的代码,简单的把它看成一次大跳转,中断前后一定会刷新缓存。然后系统内核提供给用户空间的接口都是(软)中断实现的,比如读取一个文件。即使不用内核的中断写一个死循环,但是还有最基础的硬件时间中断,比如进程和线程的调度就靠它。 这个问题分成两层,如果想写正确的 java 代码,那只需要清楚 java 里几个关键字的语义。原理的话,天然离不开 cpu 和操作系统这些底层的东西,每一层抽象都为下一层提供语义上的保证,代码最终还是老老实实的跑在硬件上。 |
34
asd123456cxz 2019-03-02 20:26:14 +08:00
@turnrut #31 感谢大佬。话说出于好奇看了下你之前的回复。。感觉好强啊,同样是自学 Java 差距巨大,不介意的话可以加个微信交流下吗?我的微信是 sul609。或者讲讲大佬你的学习路线学习途经什么的也是极好的!
|
36
choice4 2019-03-02 21:39:09 +08:00 via Android
@yuyujulin 主存主要包括本地方法区和堆区,线程工作内存主要包括 线程私有的栈区和对主存中部分变量拷贝的寄存器(包括程序计数器和 cpu 高速缓存)
|
37
lswang 2020-10-16 14:56:55 +08:00
楼主应该是不明白 while 循环里面是空的时候,为何循环停不了吧。(因为一开始我也想不明白为什么)
停不了的原因是 JIT 作祟了。楼主可以在 java 运行参数中加上 -Xint,while 里面即使为空,也是可以结束的 |