因为一直没使用过,故而没有理解 volatile 的含义。近来好好研究了下。
volatile 是与同步有关的关键字,可以实现轻量级的同步。好处是使用简单,直接修饰变量或者函数即可,缺点是无原子性,无法实现计数器等。
编译器或者 JVM 会做一个优化:在寄存器或线程空间内,保存某个变量的副本,即某个变量可能在多处存在,那么,每个线程看到的值,有可能是不同的。清楚了这一点,就不难理解 volatile 存在的含义:提示编译器或者 JVM,该变量不能被保存为多份,即,该值有可能被其它线程并发更新,所以,每次读取的时候,都必须去内存中读取,同时,如果有更新,也必须同步更新原始位置。
当然,同步的问题解决了,带来的负面影响就是,性能会有影响。但是,相对于加锁,其对性能的影响应该较低。说应该,也是因为编译器或者 JVM 对锁优化的缘故,无法保证每次测试的结果都一致。
更重要的问题,是要理解该关键字的使用场景。《Java 理论与实践:正确使用 volatile 变量》一文总结了两点,借用下:
1. 对变量的写操作不依赖于当前值
所谓“依赖当前值”,比如说 i++,这就是要依赖当前值,要取出当前值后才能加 1 。为什么不能依赖当前值呢?因为 volatile 无原子性。我们都知道,对于 i++ 这一个操作,其实包含了读取、修改、写入三步操作。假如多个线程都要执行增量操作,即使有 volatile 关键字修饰,其修改后的值也有可能丢失。
2. 该变量没有包含在具有其它变量的不变式中
对于文字表述,咱更喜欢代码来得实在:
public void testClass {
private int upper;
private int lower;
public void setUpper(int upper) {
if (upper < lower) {
//error
}
this.upper = upper;
}
public void setLower(int lower) {
if (lower > upper) {
// error
}
this.lower = lower;
}
}
如果原始值是(0,10),两个线程,同一时间调用 setUpper(5) 和 setLower(6),有可能最后的结果是(5, 6)。即使使用 volatile 修饰,结果也一样。这当然不是我们希望的结果。
这时就很好理解第 2 点了,即这两个变量的修改,都依赖于对方。其实,这两点可以汇总成一句话:变量真正独立于其它变量和自己以前的值时,才能使用 volatile。