首先编译器执行指令的时候会存在指令重排的情况,以便于提高指令的执行速度。就像是考试一样,肯定是先把会的题做完再做难的题目。什么时候会发生重排,也就是两个指令之间不会存在依赖性,例如: a = 1; y = a+1。这两个语句就不会发生指令重排,因为y的值依赖a的值;但是如果是: a =1 ; y = 2 。这两个语句就会发生,因为不存在依赖。
在单线程的条件下,指令重排不会影响到最终的结果,也就是数据的一致性可以得到保证;但是再多线程的条件下,各种线程交替执行,两个线程间使用的变量能否保证一致性就不能得到保证。
案例一:
x,y=0
线程一 | 线程二 |
x=a | y=b |
b=1 | a=3 |
在上述条件下,如果正常的话,那么执行结束之后,x和y的值应该还是0;但是由于存在指令重排,那么线程一可以先执行b=1 =>x=a; 线程二可以先执行a=3 => y=b, 最终的结果就是 x =3, y=1。
案例二:
public class SortSeqDemo {
int a = 0;
boolean flag = false;
public void method01() {
a = 1;
flag = true;
}
public void method02() {
if (flag) {
a = a+5;
System.out.println(a);
}
}
}
在这个案例中, 如果执行method01() 方法的时候, a=1; flag = true; 这两个语句的执行顺序不能被保证,所以另一个线程执行method02()方法最终打印出来的a的值可能存在两种情况, a = 6 or a = 5。
而volatile就可以解决指令重排,它使用的是内存屏障进行解决的,所谓的内存屏障是一个cpu命令,它有两个作用保证特定的执行顺序,保证可见性,通过在volatile 指令前后增加内存屏障从而解决指令重排问题。