1、什么是指令重排?
计算机执行指令的过程中会经过程序编译器编译形成的指令序列,一般而言,这个指令序列是会输出确定的结果;以确保每一次的执行都有确定的结果。但是,一般情况下,CPU和编译器为了提升程序执行的效率,会按照一定的规则允许进行指令优化,在某些情况下,这种优化会带来一些执行的逻辑问题,主要的原因是代码逻辑之间是存在一定的先后顺序,在并发执行情况下,会发生二义性,即按照不同的执行逻辑,会得到不同的结果信息。
2. 数据依赖性
主要指不同的程序指令之间的顺序是不允许进行交换的,即可称这些程序指令之间存在数据依赖性。
进过分析,发现这里每组指令中都有写操作,这个写操作的位置是不允许变化的,否则将带来不一样的执行结果。
编译器将不会对存在数据依赖性的程序指令进行重排,这里的依赖性仅仅指单线程情况下的数据依赖性;多线程并发情况下,此规则将失效。
3、as-if-serial语义
不管怎么重排序(编译器和处理器为了提高并行度),(单线程)程序的执行结果不能被改变。编译器,runtime 和处理器都必须遵守as-if-serial语义。为了遵守as-if-serial语义,编译器和处理器不会对存在数据依赖关系的操作做重排序,因为这种重排序会改变执行结果。但是,如果操作之间不存在数据依赖关系,这些操作可能被编译器和处理器重排序。
分析: 关键词是单线程情况下,必须遵守;其余的不遵守。
4. 在多线程下的指令重排
多线程情况下,下面代码指令重排是否会产生不同的结果?
上述的代码,在单线程情况下,执行结果是确定的, flag=true将被reader的方法体中看到,并正确的设置结果。 但是在多线程情况下,是否还是只有一个确定的结果呢?
在不同的线程之间共享变量;由于执行顺序、CPU编译器对于程序指令的优化等造成了不确定的执行结果。
5、指令重排的原因
主要还是编译器以及CPU为了优化代码或者执行的效率而执行的优化操作;应用条件是单线程场景下,对于并发多线程场景下,指令重排会产生不确定的执行效果。
6、如何防止指令重排
volatile关键字可以保证变量的可见性,因为对volatile的操作都在Main Memory中,而Main Memory是被线程所共享的,这里的代价就是牺牲了性能。
volatile还有一个作用就是局部阻止重排序的发生,对volatile变量的操作指令都不会被重排序,因为如果重排序,又可能产生可见性问题。
在保证可见性方面,锁(包括显式锁、对象锁)以及对原子变量的读写都可以确保变量的可见性。但是实现方式略有不同,例如同步锁保证得到锁时从内存里重新读入数据刷新缓存,释放锁时将数据写回内存以保数据可见,而volatile变量干脆都是读写内存。
7、可见性
这里提到的可见性是指前一条程序指令的执行结果,可以被后一条指令读到或者看到,称之为可见性。反之为不可见性。这里主要描述的是在多线程环境下,指令语句之间对于结果信息的读取即时性。