首先是一段简单的多线程代码
public class VolatileTest {
private boolean flag = true;
public boolean isFlag() {
return flag;
}
public void setFlag(boolean flag) {
this.flag = flag;
}
public static void main(String[] args) throws InterruptedException, IOException {
VolatileTest volatileTest = new VolatileTest();
Thread thread =new Thread(() -> {
int i = 1;
while (volatileTest.isFlag()){
i++;
}
System.out.println(i);
});
thread.start();
Thread.sleep(2000);
volatileTest.setFlag(false);
System.out.println("设置结束标示");
}
}
运行main方法后,我们发现线程并不能被有效的终止,这其中有两个原因:1、cpu高速缓存2、JIT优化
在java中,我们通常把线程独享的区域称为工作内存(栈),而把线程共享的区域称为主内存(堆),现代cpu为了提升处理效率,会在cpu和内存之间添加三级cpu高速缓存,因为cpu高速缓存,导致线程中不能读取到共享变量的变化(短时间内),导致可见性问题,而JIT编译器则会对我们的代码进行优化,查看JIT优化后的汇编代码,可以使用JITwatch(当然前提是看得懂汇编),优化后的伪代码大概是这样
boolean f = demo1.flag;
if(f){
while(true) {
i++;
}
}
这两个原因导致demo中的线程不能正确终止,除了给flag变量添加volatile关键字,我们还可以使用以下手段
1、如果是32的jdk,则可以在运行参数上加上-client,客户端模式中jdk不会对我们的代码进行JIT优化
2、64位jdk没有client运行模式,client和server的主要区别在于JIT优化,server模式下可以通过-Djava.compier=NONE关闭JIT优化
3、使用synchronized关键字,所有同步操作都必须保证原子性和可见性,所以在同步块中发生的变化会立马写回主存,我们需要对修改flag值和读取flag值的代码进行同步
4、while循环中,调用sleep()方法或者ew byte[1024*1024*1],让出cpu,JVM针对现在的硬件水平已经做了很大程度的优化,基本上很大程度的保障了工作内存和主内存的及时同步,相当于默认使用了volatile。但只是最大程度!在CPU资源一直被占用的时候,工作内存与主内存中间的同步,也就是变量的可见性就会不那么及时。CPU一直被占用的时候,数据的可见性得不到很好的保证,就像上面的程序一直循环做i++;运算占用CPU,而如果调用sleep()或者new byte[1024*1024*1]操作来说,CPU已经不是主要占时间的操作,真正的耗时应该在内存的分配上(因为CPU的处理速度明显快过内存,不然也不会有CPU的寄存器了),所以CPU空闲后会遵循JVM优化基准,尽可能快的保证数据的可见性,从而从主存同步is变量到工作内存,最终导致程序结束。
JVM指令重排序遵从if-as-serial ,即保证单个cpu中最终结果不变,多cpu多线程时,多个内存区间进行交互势必会有问题。为了解决这个问题,java语言提出内存模型的相关规范,定义了程序在每个点上可以读取到什么值,官网文档地址:https://docs.oracle.com/javase/specs/jls/se8/html/jls-17.html#jls-17.4。
java内存模型的定义
happen-before原则(先发生先生效)
JVM规范对volatile的定义,官网文档地址:https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html,通过javap命令反编译字节码文件,反编译后可以看到volatile变量有 ACC_VOLATILE访问控制符,在JVM规范中,可以看到对ACC_VOLATILE访问控制符修饰的变量是禁用缓存的。
综合下来,volatile关键字,根据java内存模型规范,对volatile变量v的写操作,对其它线程后续对v的读操作保持可见,为了满足规范,JIT有时候就不会对volatile修饰的变量进行优化,也就是不进行指令重排序;根据jvm规范,volatile变量v读操作总是能及时读取到主内存的最新值。
来源:oschina
链接:https://my.oschina.net/u/4325002/blog/4172137