一、volatile的两个作用
- 在多处理器开发中保证了共享变量的可见性。(当一个线程修改共享变量的值时,会将值写回内存,另一个线程读该变量时,会从主存中读取,而不是cpu缓存)。
- 禁止指令重排序。
二、用volatile和不用volatile的区别
见一个例子:
public class VolatileTest {
private long a = 1;
private volatile long b = 1;
private Thread mThread1 = new Thread(new Runnable() {
public void run() {
while (true) {
if (a == 1) {
a = 2;
System.out.println("a = 2");
}
// if (b == 1) {
// b = 2;
// System.out.println("b = 2");
// }
}
}
});
private Thread mThread2 = new Thread(new Runnable() {
public void run() {
while (true) {
if (a == 2) {
a = 1;
System.out.println("a = 1");
}
// if (b == 2) {
// b = 1;
// System.out.println("b = 1");
// }
}
}
});
public void test() {
mThread1.start();
mThread2.start();
}
public static void main(String[] args) {
VolatileTest volatileTest = new VolatileTest();
volatileTest.test();
}
}
运行上述代码会发现,只对a进行修改,程序不一会就不打印值,但程序还在运行;只对b进行修改,程序就会一直打印值;同时对a和b进行修改,程序也会一直打印值。
现象分析:
- 不用volatile修饰,即只对a修改的情况。cpu在读取a的值时,会首先从主存中找,因此一开始也会打印值,然后很快,它会从CPU缓存中找,缓存的中的数据是自己写进去的,因此之后就会一直判断不通过,就不会再打印值了。
- 用volatile修饰,即只对b修改的情况。cpu读取b的值时,会直接去主存找,因此,其他线程对b的修改,当前线程读取时会获取更新的值。
- 按道理说,如果同时对a和b修改,应该是一段时间后不打印a的值,但会一直打印b的值。实际上a和b的值都会打印。(这里可能的解释是:用volatile修饰的变量会使其对应的整个cpu缓存行失效,这里有可能是a和b在一个缓存行中,导致如果对b修改,那么对a的读取也是从主存中读。)
- 补充一点,System.out.println()会导致线程本地缓存失效,即影响内存可见性,下一次读会从主存中读。第1点中,有输出语句,那为什么对a值的修改未体现可见性?考虑第一个线程,判断通过后,会对a值修改,此时a值为2,下一次读取是从主存中读取,判断不通过,但是本地线程又开始缓存a的值,本地缓存a的值为2,下一次就是从缓存中读取了。虽然线程1和2是并发的,但总有一段时间,线程1会执行多个循环,导致线程1的本地缓存会缓存自己修改的值。线程2也是类似的。