1.可见性
JVM定义了线程与主内存之间的抽象关系:共享变量存储在主内存,每个线程都有一个私有的本地内存,本地内存保存了该线程使用到的主内存的副本拷贝,线程对变量的所有操作都必须在本地内存中进行,而不能直接读写主内存的变量。
例如下面的程序
运行上述的代码,你会发现 在main方法打印 running is false 之后,程序并没有正常退出,而是一直在跑着 while(running) 这个死循环。但是当我们尝试把变量 running 加上 volatile 后在运行,程序就能正常退出了。 这是因为线程不会把被 volatile 修饰的变量拷贝到线程的本地内存,对变量的读写操作都绕过线程本地内存直接去访问主内存,这样就保证了变量在线程之间的可见性,因为我们访问的同一个变量,而不是变量的副本。
另外如下图,在m方法的死循环里随便打印个什么,程序也能正常结束输出 m end!但这个不是我们要讲的重点,还是关注volatile吧,O(∩_∩)O哈哈~
另外注意:volatile只能保证变量的可见性,并不能保证操作的原子性,如果要保证原子性,可以使用synchronized
2. 禁止指令重排
了解指令重排前先了解一个概念 CPU乱序 ,上代码
可以看到,在循环了3694916次后,x = 0,y = 0 ,那如果CPU没有乱序执行那么 if (x == 0 && y == 0) 这段代码永远不会为 true,
System.out.println(String.format("第%s次 ( %s , %s ) ", i, x, y));
这段代码也永远不会执行,那么CPU为什么要乱序执行呢?提高效率,就和你上厕所要拿着手机一样,就和你烧开水的时候可以去听歌一样,你不是等着开水烧开了再去听歌,提高生活效率。
那CPU层面如何禁止重排序呢?
答:内存屏障,对某部分内存操作时前后添加屏障,屏障前后的操作不可以乱序执行。
JVM是通过对volatile关键字修饰的对象前后添加屏障实现禁止指令重排序的,在volatile写操作前后加StoreStoreBarrier,在volatile读操作前后加LoadLoadBarrier。
另外JVM规定重排序必须遵守8条hanppens-before原则。
3. DCL单例需不需要加volatile?
public class Demo2 {
private static Demo2 INSTANCE;
private Demo2() {}
public static Demo2 getInstance() {
if (INSTANCE == null) {
synchronized (Demo2.class) {
if (INSTANCE == null) {
INSTANCE = new Demo2();
}
}
}
return INSTANCE;
}
}
这段代码 private static Demo2 INSTANCE 需不需要加volatile?
未完待续....