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

  • 偶然在对项目使用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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。