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

请教一个关于 volatile 数组的问题

  •  
  •   LittlePaper · 2018-05-11 18:01:56 +08:00 · 2940 次点击
    这是一个创建于 2425 天前的主题,其中的信息可能已经有所发展或是发生改变。

    在网络上搜索到的大部分的结论都是说 volatile 修饰的是数组的引用,不能保证数组元素的可见性,我写代码测试了一下:

    public class VolatileTest {
        private volatile boolean[] running = {true};
    
        public void test() throws InterruptedException {
            new Thread(() -> {while (running[0]) {}}).start();
            Thread.sleep(1000);
            running[0] = false;
        }
    
        public static void main(String[] args) throws InterruptedException {
            new VolatileTest().test();
        }
    }
    

    上述代码运行是可以正常退出的,但如果去掉 volatile,则无法退出循环。这就与上述的结论矛盾了?

    12 条回复    2018-05-11 23:19:26 +08:00
    momocraft
        1
    momocraft  
       2018-05-11 19:06:07 +08:00
    "不保证" 不是 "保证不"。试图用实验证明线程安全多少属于 cargo cult。
    Luckyray
        2
    Luckyray  
       2018-05-11 19:07:09 +08:00 via iPhone
    1 楼终结此贴
    Luckyray
        3
    Luckyray  
       2018-05-11 19:08:23 +08:00 via iPhone
    不对,我小看了 v2exer,坐等楼下大佬翻出来编译器的代码,解释下具体实现。
    kiddult
        4
    kiddult  
       2018-05-11 20:06:54 +08:00   ❤️ 1
    加一下-XX:+PrintCompilation,你会发现 made not entrant 那行字在你设置 false 之前,直接被优化掉了
    seaswalker
        5
    seaswalker  
       2018-05-11 20:08:50 +08:00 via iPhone
    个人觉得这是提升优化,不加 volatile,编译器会优化成在 while 循环外判断一次,内部则是死循环
    seaswalker
        6
    seaswalker  
       2018-05-11 20:46:09 +08:00   ❤️ 1
    进一步说,这是 jit 编译器的提升优化,楼主可以试下下面的代码:
    public class Test {

    private static boolean flag = true;

    public static void main(String[] args) throws InterruptedException {

    new Thread(new Runnable() {
    @Override
    public void run() {
    while (flag);
    System.out.println("退出");
    }
    }).start();

    Thread.sleep(500);

    flag = false;
    }

    }

    在两种情况下可以退出,
    1. flag 加 volatile
    2. 加上 JVM 参数-Xint 关闭 JIT 编译。我觉着其实这里并没有什么可见性问题,这种单个变量的修改本身就应该是原子的,volatile 不可能加速其它 CPU 看到修改的过程,这里的 volatile 准确来说是对编译器的提示,告诉编译器这个变量是可能被修改的,不要随便搞事情。。。
    Infernalzero
        7
    Infernalzero  
       2018-05-11 21:16:48 +08:00
    应该这样写
    public class VolatileTest {
    private volatile boolean[] running = { true };

    public void test() throws InterruptedException {
    new Thread(() -> {
    final boolean a = running[0];
    while (a) {
    }
    }).start();
    Thread.sleep(1000);
    running[0] = false;
    }

    public static void main(final String[] args) throws InterruptedException {
    new VolatileTest().test();
    }
    }
    LittlePaper
        8
    LittlePaper  
    OP
       2018-05-11 21:37:18 +08:00
    @seaswalker 谢谢,确实是 JIT 引起的。原来一直以为是可见性的问题,很多文章都这么写,这次想到数组元素的可见性应该是不受 volatile 影响的,没想到结果出乎意外。不过按我的理解与猜测,可见性的问题理论上是存在的,一个线程修改了共享变量的值,另外一个线程不能立即看到,但最终能够看到,例如会定期地根据主内存的内容刷新工作内存,可能依赖于具体实现。其实我之前也发现了不用 volatile 也不一定造成循环无法退出,例如若在循环中有打印语句的话也可以退出,看来只是在这种简单的空循环下,由于编译优化造成了死循环。
    alamaya
        9
    alamaya  
       2018-05-11 21:54:06 +08:00
    volatile 两大功能,一个可见性,一个指令重排
    LittlePaper
        10
    LittlePaper  
    OP
       2018-05-11 22:49:07 +08:00
    @Infernalzero 这里是原生类型( boolean ),a 是另外一个独立的变量,当然会死循环。
    seaswalker
        11
    seaswalker  
       2018-05-11 23:10:29 +08:00
    再补充几点。一个 CPU 在修改 cache line 之前首先要获得对其的排他控制权,即要向其它 CPU 发送使无效消息,而为了保证性能,每个 CPU 均有一个 Invalidate Queue 用于处理使无效消息,但是 CPU 不提供何时处理使无效消息的保证。Java 的 volatile 实现会在读时插入一个 smp_rmb(),但是 CPU 在遇到读屏障时不会马上刷新 Invalidate Queue,而是只保证顺序,这就是为什么我上面说 volatile 不会加速其它 CPU 看到修改。所以在单个变量的读写上,其实根本没必要使用 CPU 层面上的内存屏障,对付编译器的屏障足矣,这就是 Linux 内核 ACCESS_ONCE 宏的作用,然而 Java 却没得选。。。2333
    seaswalker
        12
    seaswalker  
       2018-05-11 23:19:26 +08:00
    可见性这个东西,我上面说的没有可见性问题,指的是硬件层面。我觉得 Java 里面的可见性指的是两个方面:

    1. 软件层面,编译器重排。
    2. 硬件层面上的多变量访问的顺序问题。

    可能我们说的都没错,硬件上确实没有顺序问题,而由于 JIT 的优化确实产生了"不可见"的结果,一个概念的两个层面。
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   1011 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 25ms · UTC 21:36 · PVG 05:36 · LAX 13:36 · JFK 16:36
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.