请教一个关于 volatile 数组的问题 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
LittlePaper
V2EX    Java

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

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

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

    public class VolatileTest { private volatile boolean[] running = {true}; public void test() throws InterruptedException { nw 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:0   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     Solana     5410 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 26ms UTC 01:25 PVG 09:25 LAX 18:25 JFK 21:25
    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