volatile修饰对象和数组能保证其内部元素的可见性?

题目描述

volatile修饰对象和数组时,只是保证其引用地址的可见性,可为什么我加了volatile之后下面的代码会马上打印“结束”,如果不给数组加volatile就永远不会打印。volatile修饰对象和数组时,线程对其域或元素操作的详细步骤是什么?求大神指点

相关代码

// 请把代码文本粘贴到下方(请勿用图片代替代码)
public class b {

public static volatile int[] ints = new int[5];
public static void main(String[] args) throws Exception {
    Object o = new Object();
    new Thread(() -> {
        //线程A
        try {
            TimeUnit.MILLISECONDS.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
       ints[0] = 2;
    }).start();
    new Thread(() -> {            //线程B
        while (true) {
            if (ints[0] == 2) {
                System.out.println("结束");
                break;
            }
        }
    }).start();
}

}

阅读 12.4k
6 个回答

我把题主的问题在stackoverflow上问了一下,问题连接,有人也给出了比较靠谱的答案:

clipboard.png

然后我又在《Java并发编程实战》一书中找到了与上面结论类似的描述:(3.1.4节)

clipboard.png

不清楚什么原理,我就用结论解释一波,ThreadA在读取ints[0]时,首先要读取ints引用,这个引用是volatile修饰的,在读取这个ints引用时,所有变量都会从主存读取,其中就包含ints[0]

新手上路,请多包涵

volatile关键字不能保证数组对象元素可见性,这个是因为Java数组在元素层面的元数据设计上的缺失。在ConcurrentHashMap(1.8)中,内部使用一个volatile的数组table保存数据,Doug Lea每次在获取数组的元素时,采用Unsafe类的getObjectVolatile方法,而不是直接访问下标。这个方法就是对数组元素volatile语义的实现。
至于你这种现象为什么出现,我想可能和操作系统在实现缓存时并不是只读取单条数据而是一整块数据有关。

参考链接

先放一张图:
clipboard.png

首先如果不给数组加volatile就永远不会打印这个理解是错误的,就算不加volatile,cpu在空闲的时候也会将int[0]的值从线程缓存刷新到主存,只是while(true)太快了,导致cpu一直没空。你可以试试看加一行代码后是什么结果:

if (ints[0] == 2) {
    System.out.println("结束");
    break;
}
System.out.println("waiting");

因为System.out.pringln()源码中有synchronized关键字,是很耗时的,这时候cpu就有空去刷新int[0]的值到主存了了。
volitile的作用就是强制将int[0]的值刷新到主存中,保证其在各个线程中的可见性。
over

volatile所谓的可见性就是直接读取主存, 也就是说忽略CPU缓存

新手上路,请多包涵

请用下面代码再试下

public static volatile int[] ints = new int[5];
public static void main(String[] args) throws Exception {
    Object o = new Object();
    new Thread(() -> {
        //线程A
        try {
            TimeUnit.MILLISECONDS.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
       ints[0] = 2;
    }).start();
    new Thread(() -> {  
        //线程B
        int[] ref=ints;
        while (true) {
            if (ref[0] == 2) {
                System.out.println("结束");
                break;
            }
        }
    }).start();
}
 

}

你这个demo不能说明问题,volitale关键字用代码很难测出来

public static int[] ints = new int[5];
public static void main(String[] args) throws Exception {
    Object o = new Object();
 new Thread(() -> {
        //线程A
 try {
            TimeUnit.MILLISECONDS.sleep(100);
 } catch (InterruptedException e) {
            e.printStackTrace();
 }
        ints[0] = 2;
 }).start();
 new Thread(() -> {            //线程B
 while (true) {
            try {
                Thread.sleep(0L);
 } catch (InterruptedException e) {
                e.printStackTrace();
 }
            if (ints[0] == 2) {
                System.out.println("结束");
 break; }
        }
    }).start();
}

稍微改一下就能正常结束

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题