讨论/技术交流/题目求助|字节跳动一面问题|读取volatile变量会影响其他no-volatile变量在工作内存的值吗?/
题目求助|字节跳动一面问题|读取volatile变量会影响其他no-volatile变量在工作内存的值吗?

面试官分享的代码,为什么下面的代码中线程1会正常停止,完全想不明白,代码如下:

public class Vol {

    boolean run = true;
    volatile int s = 1;
    public static void main(String[] args) throws InterruptedException {

        Vol v = new Vol();
        //thread 1
        new Thread(() ->{
            while (v.run) {
               
                int a = v.s;    //如果不注释这行,线程1无法中止
            }
        }).start();
        //thread 2
        new Thread(() ->{
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            v.run = false;
            System.out.println("set run false");
        }).start();
    }
}

代码如上,while(run){} 判断线程是否继续循环,run是一个非volatile变量,因此一般情况线程1无法读取到线程2对run的修改,所以无法停止;但是如果在while中加入int a = v.s, v.s是一个volatile变量,线程1就可以停止了。代码里只对v.s进行读取难道也会读取主内存里run的值吗?

更新:
感谢各位的解答和提供信息,目前比较合理的回答:

https://www.zhihu.com/question/348513270

https://stackoverflow.com/questions/67233073/does-reading-a-volatile-variable-affects-the-value-of-other-no-volatile-variable

8
共 34 个回复

volatile读的内存屏障策略是"volatile读;LoadLoad;LoadStore", 应该是LoadLoad屏障影响的吧

5
3

其实加不加那句话影响了编译器的优化,导致了不同的结果.你可以看看这个, https://www.zhihu.com/question/348513270

2

tread1代码的表面执行顺序:

load v.run
compare v.run
load v.s
store a

load v.run
compare v.run
load v.s
store a

load v.run
compare v.run
load v.s
store a

……

如果没有对volatile变量读取的话,在编译器、处理器的优化下,tread1的执行顺序可能就变成了

load v.run
compare v.run
load v.s
store a

compare v.run
load v.s
store a

compare v.run
load v.s
store a

……

在读volatile变量s之前,插入一个lfence(LoadLoad)内存屏障,这样不论是在JVM层面、编译器层面甚至是机器码层面,lfence之后的load不会被重排序到lfence之前,所以在读取了volatile变量之后,tread1的执行顺序一定是这样:

load v.run
compare v.run
LoadLoad
load v.s
store a

load v.run
compare v.run
LoadLoad
load v.s
store a

load v.run
compare v.run
LoadLoad
load v.s
store a

……
1

看完解答我还是有点不理解为什么在线程1中加上

int a = v.s;

就可以停下了...
难道是因为 v.s 是 volatile, 所以线程1读取 v.s 变量的时候为了保证 HB/HA,所以刷新了线程本地内存中的 s 变量,顺带刷新了 run 变量?所以循环就能读取到 run 为false了吗?
望解答...刚开始学java并发理解不够深入...

1

首先x86架构下面对volatile的读,其实处理器是不会增加任何内存屏障的,所以不是处理器重排。其实这个问题也不能被hp原则覆盖,因为没有volatile的写,不构成hp原则。

最后缓存行肯定也是不对的,不可能永远不刷新,而且中间填充后任然可以复现

我加了一个storestore屏障,没有用volatile,也可以停下来

所以可以证明应该是编译器重排,具体怎么重排的,我尝试反汇编了下,但是没有找到具体相关代码

以上内容大佬给的回答。

美团有个文章说了用unsafe的putordeobject可以带上一个storestore内存屏障

替换了volatile的读,也奏效

以上。

1

虽然没了解过java,但是感觉这个回答靠谱,直觉上还涉及到copy on write

1

没有volatile关键字修饰的变量,只是具有不可见的可能性,但并不保证一定不可见,也不能保证一定可见。
程序运行的结果就不确定了

1

因为system.out加锁了。你把他注释了流停不下来了。加锁的语句解锁都要刷内存

1

在Stackoverflow上问到的一个答案,求大佬解释分析一下:
image.png
image.png
image.png
按他这个说法,这段代码整个就是不确定的了,即使我没有加上int a = v.s, 线程1还是可能会停下来,因为没有happend-before?

1