看网上都说,线程里面保存着变量的副本,无法感知其他线程对共享变量的修改。 由于 p 没有用 volatile 修饰,while 会一直执行,但实际运行显示,即使没有 volatile 线程也会立即感知到变量的修改,是什么原因呀。 var p = false Thread({ while (!p) { println(1) } })
p = true;
Thread.sleep(1000)
1
bthulu 2022-12-03 14:35:49 +08:00
线程没有保存副本, 是 cpu 缓存里缓存了这个变量.
现在不都是多核 cpu 么, 每个核心的缓存都是独立的. CPU1 缓存了这个变量, CPU2 去改掉了, 你加了 volatile, CPU2 就会通知 CPU1 说你那个变量失效了不要缓存了, 不加的话, CPU2 就不会通知 CPU1. |
2
bthulu 2022-12-03 14:37:30 +08:00
然而现实中, 99.9999999%的情况下, 在 CPU2 修改这个变量的时候, CPU1 里的这个变量的缓存早就被刷出去了, 所以你加不加 volatile, 其实问题都不大, 出问题的概率比你中百万大奖还要低.
|
3
dbskcnc 2022-12-03 14:49:54 +08:00 via Android 1
@bthulu 不要误导, volatile 主要给编译器看的, volatile 会取消很多优化, 缓存只是其中之一, 还有代码分支消除 /合并等
|
7
reallynyn 2022-12-03 15:01:32 +08:00
都过了 20 年了还看到初学者在问这种问题。
volatile 只是告诉编译器这个变量每次都得去内存取值,而非把取值流程优化掉。 比如你在一段代码中多次取值该变量,同时代码段没有该变量的任何修改,那么编译器可能会以第一次取值为准,其他地方的取值就被优化掉了。 volatile 不是原子操作,不能保证线程争抢性。 |
9
wangyu17455 2022-12-03 15:30:55 +08:00
println 是同步方法,会引起本地缓存失效
|
10
b1ghawk 2022-12-03 15:31:33 +08:00 via Android
@qbqbqbqb volatile 在 java 中,和在其它语言中是差不多的,只是保证了"可见性",是给编译器看的。至于顺序和屏障之类的,是 happends-before 规则额外加进去的功能,并不是 volatile 内置的东西。
|
11
fhj OP @wangyu17455 换成非同步方法也会执行
|
12
xiaohusky 2022-12-03 16:02:14 +08:00
我也搞不懂
|
14
wangyu17455 2022-12-03 16:24:59 +08:00
|
15
collery 2022-12-03 18:19:10 +08:00
@wangyu17455 我测试了下你的代码 并没有。。
|
16
wangyu17455 2022-12-03 18:20:57 +08:00
@collery 啊?我本地没问题啊,windows java17
|
17
zhangdszq 2022-12-03 18:40:11 +08:00
在你的代码中,两个线程共享变量 p 。由于 p 没有用 volatile 修饰,这意味着每个线程都会创建一个 p 副本,并且它们不会直接交换信息,而是只与它们自己的副本进行通信。在没有 volatile 的情况下,线程可能无法感知其他线程对共享变量的修改。
然而,在实际运行中,你发现即使没有 volatile ,线程也会立即感知到变量的修改。这是因为,当线程访问共享变量时,Java 会自动将共享变量的值从主存中读取到本地内存中,并在执行完操作之后将值写回主存。因此,当第一个线程修改了共享变量的值,第二个线程会立即感知到这个修改,并且会读取新的值。 尽管如此,使用 volatile 修饰共享变量仍然是一个好的实践。这可以避免复杂的线程同步问题,并且可以确保线程能够立即感知到其他线程对共享变量的修改。 -- ChatGPT |
18
anonymousar 2022-12-03 19:01:36 +08:00
那么多 cpp 的书都说过了 cpp 的 volatile 与 java 的不同。 咋还有这么多半瓶子在这晃荡。
|
20
b1ghawk 2022-12-03 20:58:42 +08:00 via Android
@leonshaw 你记错了,是 happends-before 包含了 volatile ,有很多并不是 volatile 的情况也满足 happens-before 的。
volatile 本身的作用只有保证"可见",至于什么时候可见,这取决于实现。 而 volatile 有防止重排的能力,主要是因为 happends-before 将 volatile 也当成一种场景来处理了,hb 给它加了这一层功能。如果 hb 不包含 volatile ,那么 volatile 其实和重排无关了,完全与其它语言里的 volatile 一致。 |
21
b1ghawk 2022-12-03 21:00:26 +08:00 via Android
@leonshaw 长话短说,也就是 volatile 防重排的能力并不来自于其本身,而是来自于另一套 happens-before 机制,这个机制除了处理 volatile 还处理了其它的情景。
|
22
zhangdszq 2022-12-03 21:50:09 +08:00
@fhj 不完全是。volatile 关键字除了可以防止指令重排优化之外,它还有一些其他用途。首先,它确保了线程能够立即感知到变量的修改。这意味着,如果一个线程修改了一个 volatile 变量的值,其他线程能够立即感知到这个修改,而不是等到它们下一次访问该变量时才感知到。
另外,volatile 关键字还可以用于确保多线程对于共享变量的可见性。由于线程在执行过程中可能会缓存变量的值,因此,如果没有特殊指定,其他线程可能无法立即感知到某个线程对变量的修改。如果变量被 volatile 修饰,线程在修改变量时会自动清空缓存,以确保其他线程能够立即感知到变量的修改。 --- ChatGPT |
23
leonshaw 2022-12-03 21:54:54 +08:00
@b1ghawk 你说的可能是其它语言的 volatile 。我的意思是 Java 的 volatile 关键字包含了 happens-before 的语义,不是概念本身的包含。两方面,Java 里对 volatile 变量的写是一个 store-release ,读是 load-acquire 。也就是 Java 的 volatile 相当于 C 的 volatile 加上 happens-before 语义。
|
24
littlewing 2022-12-03 21:57:08 +08:00
java 的 volatile 自带了 fence 语义,可以保证内存可见性
你说的是 C/C++ 中的 volatile ,只保证 volatile 不缓存 register ,volatile 不被编译优化,编译阶段 volatile 之间的顺序;不保证内存可见性,volatile 和普通变量之间的顺序,即没有 fence 语义 |
25
littlewing 2022-12-03 21:58:35 +08:00
另外,没事别用 volatile ,直接用原子变量就行了
|