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