引入
首先我们用一段代码引出我们的问题,什么是内存可见性问题?
static class Counter {
public int flag = 0;
}
public static void main(String[] args) {
Counter counter = new Counter();
Thread t1 = new Thread(() -> {
while (counter.flag == 0) {
}
System.out.println("循环结束!");
});
Thread t2 = new Thread(() -> {
Scanner scanner = new Scanner(System.in);
System.out.println("输⼊⼀个整数:");
counter.flag = scanner.nextInt();
});
t1.start();
t2.start();
}
在这段代码中我们想让我们输入的值不是0的时候t1线程结束,那我们试试结果呢?
很明显当我们输入的时候循环并没有暂停,这时就说明我们的程序出现Bug了,而这就是我们要讲的内存可见性问题。
产生原因
内存可见性问题是由编译器优化导致的,一个线程读取,一个线程修改,修改的值并没有被读取线程读到。
编译器,虽然声称优化操作,是能够保证逻辑不变,尤其是在多线程的程序中,编译器的判断可能出现失误可能导致编译器的优化,使优化后的逻辑,和优化前的逻辑出现细节上的偏差。
jvm不知道什么时候才会进行修改,而且它的值始终没变所以将读取内存优化成了读取寄存器,因为寄存器比内存快,而这就导致了它无法识别到内存的修改,导致了误判。所以说t1的读操作不是真正的读内存。
解决方案
sleep
通过sleep()可以让循环时间延长,让其不再优化load()操作(因为相对于整个循环优化的时间无足轻重)。
但是sleep()方法代价太大了,因此引用了volatile(易变的)关键字,既然你编译器无法判别要不要优化,我就手动判别,当它修饰某个关键字的时候,不会把它优化成读寄存器。
volatile
volatile 关键字的作用主要有如下两个:
- 保证内存可见性 :基于屏障指令实现,即当一个线程修改一个共享变量时,另外一个线程能读到这个修改的值。
- 保证有序性:禁止指令重排序。编译时 JVM 编译器遵循内存屏障的约束,运行时靠屏障指令组织指令顺序。
指令重排序:也是编译器优化的一种形式,调整代码运行的先后顺序,以得到提高性能的效果。指令重排序的大前提是逻辑不变,在多线程的环境下,这里的判定可能出现失误。
votaile是用来修饰变量的,当它修饰的时候jvm就不会对这个变量进行读寄存器的优化,而我们之前的问题就可以迎刃而解了~
public volatile int flag = 0;
感谢各位的观看~如果对你有帮助的话留个关注再走吧