指令重排
指令重排是指执行代码的顺序和编写代码不一致,即虚拟机优化代码顺序,编译或运行时环境为了优化程序性能而采取的对指令进行重写排序执行的一种手段。 如果两个操作访问一个变量,而且这两个操作中有一个为写操作,此时这两个操作之间存在数据依赖(简单说就是这两个操作若互换顺序了,就会带来不同的结果)。编译器和处理器重排指令顺序时候,会遵守数据依赖,编译器和处理器不会改变存在数据依赖关系的两个操作的执行顺序。 看下面这个代码理解一下指令重排
public class HappenBefore {
private static int a = 0;
private static boolean flag = false;
public static void main(String[] args) throws InterruptedException {
//更改数据
Thread thread1 = new Thread(()->{
a=1;
flag=true;
});
//读取数据
Thread thread2 = new Thread(()->{
if(flag){
a=1;
}
if(a == 0){
System.out.println("发生指令重排"+"->"+a);
}
});
thread1.start();
thread2.start();
thread1.join();
thread2.join();
}
}
这里运行结果10次有9次都不会输出“发生指令重排这句话” 注意:这里是两个线程,所以不存在数据依赖。
发生指令重排->0
Process finished with exit code 0
就这个更改a的值的指令分为四步,第一步获取指令,第二步从内存中获取值,第三步更改这个值,第四步将更改后的值存回内存中。前三步内存中的a值是不会改变的,因此我们这里可以认为,thread1线程运行结束后,a的更改值还没来得及存回内存,cpu就将那个判断语句的指令执行了,所以才会输出那句话。(有时候也会输出a=1的情况,是因为判断a==0之后,a的更改值才存回内存,如果有兴趣可以多试几次)
volatile
volatile可以保证可见性,保证数据的同步,就是如果一个变量加了volatile修饰,那么只要改变了变量的值,就会立刻写入主存中,并通知其他线程获取最新值。 下边这个例子中没有加入volatile修饰num变量
public class TestVolatile {
public static int num = 0;
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(()->{
while (num == 0){
}
});
t1.start();
Thread.sleep(1000);
num = 1;
}
}
运行结果就是一直运行不会停止,因为每个线程都会有一个独立的工作内存,里边存放局部变量和共享变量,这里num在t1里边存值为0,后来主内存中的num被修改为1,但由于t1线程一直做死循环没有去更改t1工作内存中的num值,所以程序会一直运行,因此我们下面加入volatile修饰这个num变量,使得线程的工作内存始终和主存中的值保持一致。
public static volatile int num = 0;