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

volatile 有个疑惑

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

    看网上都说,线程里面保存着变量的副本,无法感知其他线程对共享变量的修改。 由于 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   ·   实用小工具   ·   4099 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 23ms · UTC 05:18 · PVG 13:18 · LAX 21:18 · JFK 00:18
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.