- 偶然在对项目使用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
修饰的时候,一样有办法可以打印出来
网友评论