一.Volatile
Volatile是一个很古老的关键字,几乎在JDK初始版本时就已经存在。先来简明介绍下Volatile变量:
1. Volatile主要用来保证线程间变量的可见性:
线程对变量进行修改后,会立刻写回主内存
线程读取变量时,从主内存读取
2. Volatile不能保证原子性,这也是Volatile变量逐渐退出历史舞台的原因。volatile在某些方面确实优于synchronized,因其可以规避synchronized带来的线程挂起、调度的开销。但其应用场景十分狭隘,随着synchonized性能提高,服务器性能更加突出(对普通变量读取频率不是极高的情况下,虚拟机也能达到类似volatile及时写回主内存的作用),volatile也就无用武之地了。
3. volatile的适用场景:
书籍以及专家建议我们远离volatile。
volitele用于对变量的写操作不依赖于当前值且该变量没有包含在其他具体变量的不变式中
4. 禁止指令重排序,这也是本文的关键了;
二.指令重排序(happens-before)
1.何为指令重排序?
编译器或者运行环境对指令重新排序以提高程序执行性能,比如:
```
byte[] a
a=new a[10000];
flag=false;
```
由于初始化一个a的对象相对于写入flag需要更多的时间,为了充分利用CPU,虚拟机会重排序指令,对flag的写操作有可能排序到a的初始化之前,当然,这样做的前提是遵循“as-if-serial”(不管怎么重排序,单线程的执行结果不能被改变)
2.一个简单的happens-before实例
public class Reorder{
private static int a=0;
private static boolean flag=false;
public static void main(String[] args) throws InterruptedException {
for(int i=0;i<1000;i++){
ThreadA threadA=new ThreadA();
ThreadB threadB=new ThreadB();
threadA.start();
threadB.start();
threadA.join();
threadB.join();
a=0;
flag=false;
}
}
static class ThreadA extends Thread{
public void run(){
a=1; //A
flag=true; //B A与B不存在数据依赖所以可能发生重排序
}
}
static class ThreadB extends Thread{
//锁起该方法,控制变量
public synchronized void run(){
if(flag){
System.out.println(a);
}
}
}
实例分析
if(flag){
System.out.println(a);
}
可能出现在1,2,3位置,结果分别为 1=无输出,2=无输出,3=1
如图所示,在进行重排序的情况下,输出结果分别为1=无输出,2=0,3=1
所以,只要在1000循环中输出结果中有几次为0 就代表了happen-before发生了。
很遗憾,所有的结果都为1或者空,也就意味着happen-before没有发生。
划重点(原因)
指令重排发生在处理器平台,对于Java来说是看不到的,因为Jvm基于线程栈,所有的读写都对应了store操作,而Intel 64/IA-32架构下(因为Intel出色的安全稳定性,几乎所有的服务器都是Intel平台把)处理器不需要LoadLoad、LoadStore、StoreStore屏障,因为不会发生需要这三种屏障的重排序。所以,store操作之间是不会重排序的。
备注:本人穷学生资源有限,找不到其他处理器实验,大佬可以自己尝试
附录(以供自己翻阅查询):
happens-before规则:
1.程序顺序规则:一个线程中的每个操作,happens-before于该线程中的任意后续操作
2.监视器锁规则:对一个锁的解锁,happens-before于随后对这个锁的加锁
3.volatile变量规则:对一个volatile域的写,happens-before于任意后续对这个volatile域的读
4.传递性:如果A happens-before B,且B happens-before C,那么A happens-before C
5.start规则:如果线程A执行操作ThreadB.start()(启动线程B),那么A线程的ThreadB.start()操作happens-before于线程B中的任意操作
6.join规则:如果线程A执行操作ThreadB.join()并成功返回,那么线程B中的任意操作happens-before于线程A从ThreadB.join()操作成功返回。