最近在看 java 的 volatile 这个东西,写了一块代码想测试一下,代码如下:
我的理解是,如果变量 a 没有 volatile 修饰,那么理论上就应该线程 1 打印一次,其他线程空转不再打印
但目前发现有一些情况:
1 、如果不加 Thread.sleep 这行代码,程序能直接打印到 100 ,加了 sleep 才是死循环
2 、去掉 sleep ,但把打印语句换成 System.out.println(Thread.currentThread().getName()+":"+su.getA()); 结果也会变成死循环
所以想请教下 v 友两个问题
1 、为什么我没加 volatile 也能打印到 100
2 、为什么换了个输出语句就又打印不到 100 了。。。
1
wuyiccc 16 天前
没看懂。。。
|
2
Hatter OP @wuyiccc 就是理论上不用 volatile 修饰 a 的话,线程内对 a 做了修改,变量 a 的变动对线程 2 和 3 应该是不可见的,理论上线程 2 和 3 就是应该一直死循环才对;但我发现有时候居然能打出来,然后做了一点看起来不相关的修改之后又打印不出来了
|
3
wolfie 16 天前
volatile 是 内存可见性问题,不用 volatile 修饰,不代表永远无变化感知,只是延迟。
|
4
psjay 16 天前
不可见的意思是:不立即可见。
你这个 Demo 用来测试 volatile 没有意义,即便你加了 volatile ,对 a 的访问也无法保证原子性。 |
5
liprais 16 天前
"就是理论上不用 volatile 修饰 a 的话,线程内对 a 做了修改,变量 a 的变动对线程 2 和 3 应该是不可见的"
理论不是这样的 https://jpbempel.github.io/2015/05/26/volatile-and-memory-barriers.html |
8
cloudzhou 15 天前
保证可见性,只是说一定能看到,happens before
但不是说,一直不可见,理论上,随着内存的同步周期变化,是能感知到变化的 |
9
lmq2582609 15 天前 3
你的问题应该和编译器有关。java 好像在 1.8 开始就默认使用分层编译了。
它编译的大概步骤是:解释阶段 -> C1 编译器 -> C2 编译器 如果你只使用 C1 编译器的话代码执行是没有问题的,你可以增加 VM 参数尝试:-XX:TieredStopAtLevel=3 。因为 C1 编译器的 level 是 1 ,2 ,3 ,所以设置-XX:TieredStopAtLevel 不超过 3 ,就不会使用 C2 编译器。 如果你只想使用 C2 编译器,你可以设置-XX:TieredStopAtLevel=4 。不过默认就会启动,可以不必尝试了。 当然,对于 volatile 的处理,C1 和 C2 都是遵循 java 内存模型的。 |
11
Hatter OP @wangliran1121 感谢回复,不懂的东西越来越多了 T T
|
12
zengyufei 11 天前
针对 “1 、为什么我没加 volatile 也能打印到 100”,我来说一个正确答案:因为 System.out.println() 的调用。
参考: https://blog.csdn.net/weixin_47474875/article/details/128916512 |
13
zengyufei 11 天前
针对 Thread.sleep 有无导致结果不同,我也从 GPT 那里得到了答案。
忙循环导致子线程 a 无暇从主线程 a 那里得到更新。 大白话就是:我一直在忙,没有空去新的东西。 给你对比实验感知一下这个事情: 1 、不加 volatile 2 、Thread.sleep(100); 这句留着 即与你图片的代码完全一致即可。 运行只能 thread1 打印了一下。 对比实验动作 1:thread2 的 while 循环代码块加入 Thread.sleep(1); 为什么先从 thread2 开始加,因为你 thread1 执行完毕 a+1 了,下一个肯定是 thread2 先执行。 你可以先加 thread3 ,运行结果肯定没变化。 先从 thread2 开始加,会得到结果 thread2 也打印了语句。 对比实验动作 2:thread3 的 while 循环代码块加入 Thread.sleep(1); 运行结果大概打印 3-20 条,具体看概率。 因为 System.out.println 会模仿 volatile 功能,导致 thread1 偶尔会更新 a ,最后 thread1 会陷入忙循环。 对比实验动作 3:最后 thread1 的 while 循环代码块加入 Thread.sleep(1); 运行,你会正确打印 1-100 ,这是程序最初的目的。 最后,你自己总结一下。 |