public class VisibilityDemo {
private boolean flag = true;
public static void main(String[] args) throws InterruptedException {
VisibilityDemo demo = new VisibilityDemo();
System.out.println("开始运行");
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
int i = 0;
while (demo.flag){
i++;
}
System.out.println(i);
}
});
thread1.start();
TimeUnit.SECONDS.sleep(2);
demo.flag = false;
System.out.println("运行结束");
}
}
如上,按照这段代码的目的来说,会在两秒后打印出 i 的值并结束。但运行后并不会结束,如下图
这就是一个可见性问题
那么问题就来了
- 这段程序为什么不会正常运行
- 如何能够让它正常运行
为什么不会正常运行
缓存导致的可见性问题
每一个线程都有自己的本地变量,从内存中拷贝到CPU缓存,每个CPU核心有自己的缓存。但是CPU自身保证了缓存的一致性,缓存刷新会有延迟,但也是延迟一段时间 i 就会打印出来,不会一直不打印。JVM 的指令重排序
JVM 的运行模式分为三种:编译模式、解释模式、混合模式
编译模式:jit 将字节码提前编译为汇编
解释模式:在程序运行时,一行一行的将字节码编译为汇编
混合模式:运行过程中,对热点代码进行编译优化,与编译模式相同,其他非热点与解释模式相同
在这个例子中,由于 jit 的优化导致程序没有按照预想的运行,优化后的程序相当于以下伪代码:
public void run() {
int i = 0;
boolean flag = demo.flag;
while (flag){
i++;
}
System.out.println(i);
}
指令重排序是在汇编阶段的操作,无法通过代码直接看到,不过我们可以在 jvm 的启动参数上加上
-Djava.compiler=NONE
关闭自动优化来验证
可以看到关闭后就打印出了 i 的值
如何修改代码使其正常运行
刚刚通过关闭 jit 的优化使其正常运行了,还可以通过修改代码让它正常运行
- volatile
private volatile boolean flag = true;
jvm 规范规定了,被 volatile 关键字修饰的变量不能够进行指令重排序,也不能被CPU缓存,修改的值实时地写入内存。在这里 volatile 可以解决这个问题。
注意:volatile 是语法层面的,并不是 volatile 直接起作用,而是 jvm 规范规定了,然后厂商按照规范开发虚拟机,是在虚拟机层面实现了 volatile 涉及的功能。
- synchronized
synchronized 关键字在这里不能解决这个问题,锁可以确保同一时间只有一个线程操作 i 变量,但这里不是多线程导致的,所以不能解决这个问题。
- lock
同 synchronized 也不能解决这个问题。