volatile 有个疑惑 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
fhj
V2EX    Android

volatile 有个疑惑

  •  
  •   fhj 2022-12-03 14:28:15 +08:00 13209 次点击
    这是一个创建于 1071 天前的主题,其中的信息可能已经有所发展或是发生改变。

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

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

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

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

    -- ChatGPT
    anonymousar
        18
    anonymousar  
       2022-12-03 19:01:36 +08:00
    那么多 cpp 的书都说过了 cpp 的 volatile 与 java 的不同。 咋还有这么多半瓶子在这晃荡。
    fhj
        19
    fhj  
    OP
       2022-12-03 19:14:55 +08:00
    @zhangdszq 所以加不加都无所谓,目前 volatile 的唯一作用就是防止指令重排吗?
    b1ghawk
        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 一致。
    b1ghawk
        21
    b1ghawk  
       2022-12-03 21:00:26 +08:00 via Android
    @leonshaw 长话短说,也就是 volatile 防重排的能力并不来自于其本身,而是来自于另一套 happens-before 机制,这个机制除了处理 volatile 还处理了其它的情景。
    zhangdszq
        22
    zhangdszq  
       2022-12-03 21:50:09 +08:00
    @fhj 不完全是。volatile 关键字除了可以防止指令重排优化之外,它还有一些其他用途。首先,它确保了线程能够立即感知到变量的修改。这意味着,如果一个线程修改了一个 volatile 变量的值,其他线程能够立即感知到这个修改,而不是等到它们下一次访问该变量时才感知到。

    另外,volatile 关键字还可以用于确保多线程对于共享变量的可见性。由于线程在执行过程中可能会缓存变量的值,因此,如果没有特殊指定,其他线程可能无法立即感知到某个线程对变量的修改。如果变量被 volatile 修饰,线程在修改变量时会自动清空缓存,以确保其他线程能够立即感知到变量的修改。

    --- ChatGPT
    leonshaw
        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 语义。
    littlewing
        24
    littlewing  
       2022-12-03 21:57:08 +08:00
    java 的 volatile 自带了 fence 语义,可以保证内存可见性

    你说的是 C/C++ 中的 volatile ,只保证 volatile 不缓存 register ,volatile 不被编译优化,编译阶段 volatile 之间的顺序;不保证内存可见性,volatile 和普通变量之间的顺序,即没有 fence 语义
    littlewing
        25
    littlewing  
       2022-12-03 21:58:35 +08:00
    另外,没事别用 volatile ,直接用原子变量就行了
    iseki
        26
    iseki  
       2022-12-04 10:21:03 +08:00 via Android
    @fhj 所谓的线程本地副本,是为了和现实世界中变量可能被优化掉 /存到寄存器里 /跑到高速缓存里等情况对应。volatile 保证“可靠一致的访问”( JLS 原话),不是说不加就一定不一致了 (以上那些情况不一定会发生)
    你这个例子就是因为种种原因,以上那些情况没发生,或者不良的副作用被别的因素抵消了
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     2482 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 28ms UTC 15:24 PVG 23:24 LAX 07:24 JFK 10:24
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86