volatile是java的关键字,作用:①保证线程间的可见性;②防止指令重排。下面看一个demo,启动2个线程,一个线程读取flag变量的值,另外一个线程修改flag变量的值。
public class VolatileDemo {
private static int flag = 0;
//private volatile static int flag = 0;
public static void main(String[] args) {
new Thread(){
@Override
public void run() {
int localFlag = flag;
while (true){
if (localFlag != flag){
System.out.println("读取到被修改的flag值为:"+flag);
localFlag = flag;
}
}
}
}.start();
new Thread(){
@Override
public void run() {
int localFlag = flag;
while (true){
System.out.println("flag被修改为了:"+ ++localFlag);
flag = localFlag;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}.start();
}
}
执行2遍,区别是否有volatile的修饰,执行结果如下。
为什么使用volatile关键字,就能保证线程之间变量的可见性?它是如何做到的?如下图是该例的java内存模型。其实java内存模型的实现,是参考了CPU缓存模型。CPU缓存不一致的问题,早期是用总线加锁机制来实现,但是效率太差,很容易出现串行化的问题。后来用MESI协议,这个也很复杂,后边再说吧。
如下图java内存模型:线程的工作内存和主内存,read(从主存读取),load(将主存读取到的值写入工作内存),use(从工作内存读取数据来计算),assign(将计算好的值重新赋值到工作内存中),store(将工作内存数据写入主存),write(将store过去的变量值赋值给主存中的变量)。
那volatile到底是如何保证可见的?结合上图的java内存模型,如果该变量是volatile修饰的,那assign操作后,一定会强制保证立马执行store + write,刷回到主内存里去。同时会让其他线程工作内存里的flag变量过期,从主内存重新读取。