美文网首页
volatile修饰数组或引用对象的问题

volatile修饰数组或引用对象的问题

作者: slowwalkerlcr | 来源:发表于2019-07-10 19:43 被阅读0次
    • 偶然在对项目使用sonarLint扫描的时候,得到警告“Non-primitive fields should not be "volatile"”,意思就是非基本字段不应该用volatile修饰。其原因是volatile修饰对象或数组时,只能保证他们的引用地址的可见性。那么事实是怎样呢?我们修改数组的元素(并非修改引用地址)是否对其他线程可见呢?直接扔代码执行一番。
      1、首先不用volatile修饰array数组,发现“结束”两个字一直没打印出来,程序一直在运行,这没有任何问题,因为线程A修改了 array[0] = 2对线程B不可见
        public static int[] array = new int[10];
        public static void main(String[] args) {
            new Thread(() -> {  //线程A
                try {
                    TimeUnit.MILLISECONDS.sleep(100);
                    array[0] = 2;
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
    
            }).start();
            new Thread(()->{   //线程B
                try {
                    while (true) {
                        if (array[0] == 2) {
                            System.out.println("结束");
                            break;
                        }
                        //Thread.sleep(10);
                    }
                }catch (Exception e) {
                    e.printStackTrace();
                }
            }).start();
        }
    

    2、当加上volatile修饰array时,WTF。。。“结束”打印出来了,这说明线程A修改array[0] = 2对线程B可见呢?这和我们开始得到的观点矛盾了(volatile只对数组的引用保证可见性,这里只修改了array的元素)

     public static volatile int[] array = new int[10];
        public static void main(String[] args) {
            new Thread(() -> {  //线程A
                try {
                    TimeUnit.MILLISECONDS.sleep(100);
                    array[0] = 2;
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
    
            }).start();
            new Thread(()->{   //线程B
                try {
                    while (true) {
                        if (array[0] == 2) {
                            System.out.println("结束");
                            break;
                        }
                        //Thread.sleep(10);
                    }
                }catch (Exception e) {
                    e.printStackTrace();
                }
            }).start();
        }
    
    • 这里stackoverflow给出了比较靠谱的答案链接地址,大概意思就是说当ThreadB读取array时,因为array的引用被volatile修饰,所以ThreadB对所有变量都会从主内存去获取,当然也就包括array[0]。 所以会让人产生误解,以为是volatile修饰的数组保证了其数组的可见性,其实不然。
      image.png 《Java并发编程实战》一书给出的解释如下
      image.png
    • 其实如果不给数组加volatile就永远不会打印结束”,这种绝对的认为是错误的,volatile保证了其变量及时可见性而不会引起线程安全的问题,就算不加volatile,cpu在空闲的时候也会将array[0]的值从线程缓存刷新到主存,只是while(true)太快了,导致cpu一直没空。我们可以使用Thread.sleep(10)让出CPU让其有空将线程变量刷新到主内存中去。记得先去掉volatile修饰
     public static int[] array = new int[10];
        public static void main(String[] args) {
            new Thread(() -> {  //线程A
                try {
                    TimeUnit.MILLISECONDS.sleep(100);
                    array[0] = 2;
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
    
            }).start();
            new Thread(()->{   //线程B
                try {
                    while (true) {
                        if (array[0] == 2) {
                            System.out.println("结束");
                            break;
                        }
                        Thread.sleep(10);
                    }
                }catch (Exception e) {
                    e.printStackTrace();
                }
            }).start();
        }
    

    我们发现“结束”在没有volatile修饰的时候,一样有办法可以打印出来

    image.png

    相关文章

      网友评论

          本文标题:volatile修饰数组或引用对象的问题

          本文链接:https://www.haomeiwen.com/subject/odjkkctx.html