一旦一个共享变量(类的成员变量、类的静态成员变量)被volatile修饰之后,那么就具备了两层语义:
保证线程间的可见性
保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的,volatile关键字会强制将修改的值立即写入主存。
一个典型的例子:永不停止的循环
packagecom.itheima.basic;
// 可见性例子
// -Xint
publicclassForeverLoop {
staticbooleanstop=false;
publicstaticvoidmain(String[] args) {
newThread(() -> {
try {
Thread.sleep(100);
} catch (InterruptedExceptione) {
e.printStackTrace();
}
stop=true;
System.out.println("modify stop to true...");
}).start();
foo();
}
staticvoidfoo() {
inti=0;
while (!stop) {
i++;
}
System.out.println("stopped... c:"+i);
}
}
当执行上述代码的时候,发现foo()方法中的循环是结束不了的,也就说读取不到共享变量的值结束循环。
主要是因为在JVM虚拟机中有一个JIT(即时编辑器)给代码做了优化。
上述代码
while (!stop) {
i++;
}
在很短的时间内,这个代码执行的次数太多了,当达到了一个阈值,JIT就会优化此代码,如下:
while (true) {
i++;
}
当把代码优化成这样子以后,及时stop
变量改变为了false
也依然停止不了循环
解决方案:
第一:
在程序运行的时候加入vm参数-Xint
表示禁用即时编辑器,不推荐,得不偿失(其他程序还要使用)
第二:
在修饰stop
变量的时候加上volatile
,表示当前代码禁用了即时编辑器,问题就可以解决,代码如下:
staticvolatilebooleanstop=false;
禁止进行指令重排序
用 volatile 修饰共享变量会在读、写共享变量时加入不同的屏障,阻止其他读写操作越过屏障,从而达到阻止重排序的效果
在去获取上面的结果的时候,有可能会出现4种情况
情况一:先执行actor2获取结果--->0,0(正常)
情况二:先执行actor1中的第一行代码,然后执行actor2获取结果--->0,1(正常)
情况三:先执行actor1中所有代码,然后执行actor2获取结果--->1,1(正常)
情况四:先执行actor1中第二行代码,然后执行actor2获取结果--->1,0(发生了指令重排序,影响结果)
解决方案
在变量上添加volatile,禁止指令重排序,则可以解决问题
屏障添加的示意图
· 写操作加的屏障是阻止上方其它写操作越过屏障排到volatile变量写之下
· 读操作加的屏障是阻止下方其它读操作越过屏障排到volatile变量读之上
其他补充
我们上面的解决方案是把volatile加在了int y这个变量上,我们能不能把它加在int x这个变量上呢?
下面代码使用volatile修饰了x变量
屏障添加的示意图
这样显然是不行的,主要是因为下面两个原则:
· 写操作加的屏障是阻止上方其它写操作越过屏障排到volatile变量写之下
· 读操作加的屏障是阻止下方其它读操作越过屏障排到volatile变量读之上
所以,现在我们就可以总结一个volatile使用的小妙招:
· 写变量让volatile修饰的变量的在代码最后位置
· 读变量让volatile修饰的变量的在代码最开始位置