请教下 Java 的 volatile 以及一点多线程的疑问

120 天前
 Hatter

最近在看 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 了。。。

2054 次点击
所在节点    Java
13 条回复
wuyiccc
120 天前
没看懂。。。
Hatter
120 天前
@wuyiccc 就是理论上不用 volatile 修饰 a 的话,线程内对 a 做了修改,变量 a 的变动对线程 2 和 3 应该是不可见的,理论上线程 2 和 3 就是应该一直死循环才对;但我发现有时候居然能打出来,然后做了一点看起来不相关的修改之后又打印不出来了
wolfie
120 天前
volatile 是 内存可见性问题,不用 volatile 修饰,不代表永远无变化感知,只是延迟。
psjay
120 天前
不可见的意思是:不立即可见。
你这个 Demo 用来测试 volatile 没有意义,即便你加了 volatile ,对 a 的访问也无法保证原子性。
liprais
120 天前
"就是理论上不用 volatile 修饰 a 的话,线程内对 a 做了修改,变量 a 的变动对线程 2 和 3 应该是不可见的"
理论不是这样的
https://jpbempel.github.io/2015/05/26/volatile-and-memory-barriers.html
Hatter
120 天前
@psjay 是我对不可见的理解有问题了。。多谢
原子性这块倒是明白,并不是想测试原子性相关的情况
Hatter
120 天前
@liprais 多谢老哥,还是搞错了“是不是”的问题
cloudzhou
119 天前
保证可见性,只是说一定能看到,happens before
但不是说,一直不可见,理论上,随着内存的同步周期变化,是能感知到变化的
dongdong12345
119 天前
你的问题应该和编译器有关。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 内存模型的。
wangliran1121
119 天前
Hatter
119 天前
@wangliran1121 感谢回复,不懂的东西越来越多了 T T
zengyufei
116 天前
针对 “1 、为什么我没加 volatile 也能打印到 100”,我来说一个正确答案:因为 System.out.println() 的调用。
参考: https://blog.csdn.net/weixin_47474875/article/details/128916512
zengyufei
115 天前
针对 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 ,这是程序最初的目的。

最后,你自己总结一下。

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

https://yangjunhui.monster/t/1102734

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

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

© 2021 V2EX