volatile 的具体作用和原理可能大家都基本了解了,无非是可见性、禁止指令重排序、主内存、内存屏障等几个关键字,就不赘述了。
本来我也以为 volatile 学到这里就 OK 了,但最近发现了一个小知识点,而网上很多文章都没说到,因此特地写了这篇文章来讨论讨论。
我们都知道 volatile 能保证可见性,但它保证的是谁的可见性呢?是引用,还是对象?假如使用 volatile 来修饰 Object 或者数组,而 Object 或数组的成员被修改了,其它线程能立刻感知到吗?
为了验证这个问题,我专门写了一个测试 demo:
public class VolatileObject {
public int a;
public int b;
public VolatileObject(int a, int b) {
this.a = a;
this.b = b;
}
private static volatile boolean stop = false;
private static volatile VolatileObject object = new VolatileObject(1, 100);
public static void main(String[] args) {
// 线程 1
new Thread(() -> {
while (!stop) {
System.out.println("[a: " + object.a + "], [b: " + object.b + "]");
}
}).start();
// 线程 2
new Thread(() -> {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
object.a = 2;
System.out.println("-----------changed a");
object.b = 200;
System.out.println("-----------changed b");
stop = true;
System.out.println("-----------stop");
}).start();
}
}
复制代码
运行结果:
...
[a: 2], [b: 100]
[a: 2], [b: 100]
[a: 2], [b: 100]
-----------changed a
-----------changed b
-----------stop
[a: 2], [b: 100]
复制代码
可以发现,在线程 2 修改了 object 的成员 b 的值之后,线程 1 读取的仍然是 object 被修改之前的值。
因此,可以得出结论:volatile 修饰的是引用,而不是对象。
也就是说,如果引用改变了:
object = null; // 或者指向了另一个对象
复制代码
这种情况下,volatile 是能保证可见性的。
但如果,引用未改变,改变的是对象的值:
object.a = 2;
object.b = 200;
复制代码
这种情况下,volatile 是不保证其它线程能够立刻得知这个修改的。
因此,使用 volatile 修饰对象或者数组的时候要注意,你关心的是引用的改变,还是数据的改变?如果是引用,那么可以继续使用 volatile 没问题;如果是数据,要么再次使用 volatile 修饰对应的数据,要么直接使用 synchronized。
以上就是本文讨论的问题,水平有限,如有错漏,请指出。