1.内存可见性
当一个变量被声明volatile时,它会保证被修改的值被立刻更新到主内存中,不会优化到寄存器或缓存中
2.禁止指令重排序
针对被volatile修饰的变量的读写相关指令,是不能被重新排序的
代码演示
class Counter{
public int flag;
}
public class Thread3 {
public static void main(String[] args) throws InterruptedException {
Counter counter=new Counter();
Thread t1=new Thread(()->{
while (counter.flag==0){
//方便演示什么都不做
}
});
Scanner scanner=new Scanner(System.in);
Thread t2=new Thread(()->{
counter.flag=scanner.nextInt();
});
t1.start();
t2.start();
t1.join();
t2.join();
}
}
上述代码,t1线程在循环检查flag值,成立就执行代码,t2线程更改count值,使得线程1检查结果不成立,测试发现在t2线程代码输入结果后,线程1代码还是不会停止,这是由于t1
线程在一个无限循环中检查flag
的值,并且flag
的更新对t1
线程不可见,因此t1
线程可能会永远等待下去,即使t2
线程已经修改了flag
的值。
使用volatile解决
class Counter{
public volatile int flag; // 声明为 volatile
}
public class Thread3 {
// ... 其他代码保持不变 ...
}
使用volatile后
- 内存可见性:
volatile
关键字确保了对volatile
变量的写操作对其他线程总是可见的。当t2
线程更新counter.flag
的值时,这个更新会立即对其他线程(如t1
线程)可见。这意味着t1
线程在检查counter.flag
的值时,将总是看到t2
线程所做的最新更新。- 禁止指令重排序:
volatile
关键字还禁止了一些可能导致数据竞争的重排序。具体来说,它确保了写volatile
变量之前的所有读写操作不会被重排序到写操作之后,同时确保了读volatile
变量之后的所有读写操作不会被重排序到读操作之前。这确保了t2
线程中的读取输入和更新flag
的操作不会被重新排序,从而避免了数据竞争和不一致的行为。
因此,如果将Counter
类中的flag
字段声明为volatile
,那么t1
线程将能够正确地看到t2
线程对flag
的更新,并且不会因为指令重排序而导致数据竞争或不一致的行为。