为什么要引入volatile关键字? 观察以下这个代码以及他的运行结果.
static int flag = 0;
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
Thread t1 = new Thread(() ->{
while(flag == 0){
}
System.out.println("t1结束!");
});
Thread t2 = new Thread(() ->{
flag = scanner.nextInt();
});
t1.start();
t2.start();
}
可以观察到,即使我们以及输入了1将flag修改了,但是t1中仍然在执行flag为0的逻辑.
像这样的问题,成为内存可见性问题。
这个问题是如何产生的?
在代码的执行过程中,编译器以及JVM会对我们写的代码进行“优化”,但是这个优化在多线程时可能会出现问题。
例如,在上面的代码中,flag被修改但是t1没有察觉到,因为t1执行的是:将flag的值读取到工作内存中,在工作内存中比较flag是否等于0.由于t2中等待I/O需要时间,所以在我们输入1之前,t1的工作已经做了非常多次了,在这些非常多次的工作中,JVM发现flag的值没有被修改,且t1的代码中也没有修改flag值的操作,所以他就将操作消耗大的读取操作取消掉了,也就是只进行比较。
此时如果我们输入1,t1也不会去内存中读取被修改掉的flag的值,所以t1中所进行判断的flag依旧是0.
解决内存可见性问题——volatile
volatile的作用:
代码在写⼊volatile修饰的变量的时候,改变工作内存中变量的值,然后将改变后的值刷新到主内存中。
代码在读取volatile修饰的变量的时候,从主内存中读取volatile变量的最新值到⼯作内存中,然后再读取工作内存中的变量的值。
当一个变量被volatile修饰后,每次应用到这个变量时,都会强制与主内存同步来保证变量的值正确,这样就能解决上述的问题.
解决案例:
volatile static int flag = 0;
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
Thread t1 = new Thread(() ->{
while(flag == 0){
}
System.out.println("t1结束!");
});
Thread t2 = new Thread(() ->{
flag = scanner.nextInt();
});
t1.start();
t2.start();
}
在输入1之后,t1立刻结束,符合我们的预期。
注意:volatile不保证原子性