volatile 有个疑惑

2022-12-03 14:28:15 +08:00
 fhj

看网上都说,线程里面保存着变量的副本,无法感知其他线程对共享变量的修改。 由于 p 没有用 volatile 修饰,while 会一直执行,但实际运行显示,即使没有 volatile 线程也会立即感知到变量的修改,是什么原因呀。 var p = false Thread({ while (!p) { println(1) } })

p = true;
Thread.sleep(1000)
10637 次点击
所在节点    Android
26 条回复
bthulu
2022-12-03 14:35:49 +08:00
线程没有保存副本, 是 cpu 缓存里缓存了这个变量.
现在不都是多核 cpu 么, 每个核心的缓存都是独立的.
CPU1 缓存了这个变量, CPU2 去改掉了, 你加了 volatile, CPU2 就会通知 CPU1 说你那个变量失效了不要缓存了, 不加的话, CPU2 就不会通知 CPU1.
bthulu
2022-12-03 14:37:30 +08:00
然而现实中, 99.9999999%的情况下, 在 CPU2 修改这个变量的时候, CPU1 里的这个变量的缓存早就被刷出去了, 所以你加不加 volatile, 其实问题都不大, 出问题的概率比你中百万大奖还要低.
dbskcnc
2022-12-03 14:49:54 +08:00
@bthulu 不要误导, volatile 主要给编译器看的, volatile 会取消很多优化, 缓存只是其中之一, 还有代码分支消除 /合并等
fhj
2022-12-03 14:50:22 +08:00
@bthulu 谢谢,还有个疑惑,线程运行中,会更换 cpu 运行吗,还是永远都在一个 cpu 里运行。
fhj
2022-12-03 14:52:33 +08:00
@dbskcnc 请问,那线程到底有没有保存副本呀,如果保存的话,是用 threadlocal 自动保存的吗,
r6cb
2022-12-03 14:53:37 +08:00
@fhj 除非在代码里设置了 CPU 亲和性,否则这个线程跑在哪个核心是由操作系统说了算的
reallynyn
2022-12-03 15:01:32 +08:00
都过了 20 年了还看到初学者在问这种问题。
volatile 只是告诉编译器这个变量每次都得去内存取值,而非把取值流程优化掉。
比如你在一段代码中多次取值该变量,同时代码段没有该变量的任何修改,那么编译器可能会以第一次取值为准,其他地方的取值就被优化掉了。
volatile 不是原子操作,不能保证线程争抢性。
qbqbqbqb
2022-12-03 15:28:02 +08:00
@dbskcnc @reallynyn C/C++的 volatile 和 Java 的不是一回事
wangyu17455
2022-12-03 15:30:55 +08:00
println 是同步方法,会引起本地缓存失效
b1ghawk
2022-12-03 15:31:33 +08:00
@qbqbqbqb volatile 在 java 中,和在其它语言中是差不多的,只是保证了"可见性",是给编译器看的。至于顺序和屏障之类的,是 happends-before 规则额外加进去的功能,并不是 volatile 内置的东西。
fhj
2022-12-03 15:39:33 +08:00
@wangyu17455 换成非同步方法也会执行
xiaohusky
2022-12-03 16:02:14 +08:00
我也搞不懂
leonshaw
2022-12-03 16:12:28 +08:00
@b1ghawk 没记错的话 Java 的 volatile 包含 happens-before
wangyu17455
2022-12-03 16:24:59 +08:00
collery
2022-12-03 18:19:10 +08:00
@wangyu17455 我测试了下你的代码 并没有。。
wangyu17455
2022-12-03 18:20:57 +08:00
@collery 啊?我本地没问题啊,windows java17
zhangdszq
2022-12-03 18:40:11 +08:00
在你的代码中,两个线程共享变量 p 。由于 p 没有用 volatile 修饰,这意味着每个线程都会创建一个 p 副本,并且它们不会直接交换信息,而是只与它们自己的副本进行通信。在没有 volatile 的情况下,线程可能无法感知其他线程对共享变量的修改。

然而,在实际运行中,你发现即使没有 volatile ,线程也会立即感知到变量的修改。这是因为,当线程访问共享变量时,Java 会自动将共享变量的值从主存中读取到本地内存中,并在执行完操作之后将值写回主存。因此,当第一个线程修改了共享变量的值,第二个线程会立即感知到这个修改,并且会读取新的值。

尽管如此,使用 volatile 修饰共享变量仍然是一个好的实践。这可以避免复杂的线程同步问题,并且可以确保线程能够立即感知到其他线程对共享变量的修改。

-- ChatGPT
anonymousar
2022-12-03 19:01:36 +08:00
那么多 cpp 的书都说过了 cpp 的 volatile 与 java 的不同。 咋还有这么多半瓶子在这晃荡。
fhj
2022-12-03 19:14:55 +08:00
@zhangdszq 所以加不加都无所谓,目前 volatile 的唯一作用就是防止指令重排吗?
b1ghawk
2022-12-03 20:58:42 +08:00
@leonshaw 你记错了,是 happends-before 包含了 volatile ,有很多并不是 volatile 的情况也满足 happens-before 的。
volatile 本身的作用只有保证"可见",至于什么时候可见,这取决于实现。
而 volatile 有防止重排的能力,主要是因为 happends-before 将 volatile 也当成一种场景来处理了,hb 给它加了这一层功能。如果 hb 不包含 volatile ,那么 volatile 其实和重排无关了,完全与其它语言里的 volatile 一致。

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

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

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

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

© 2021 V2EX