一、编译器和处理器常常会对指令做重排
计算机在执⾏程序时,为了提⾼性能,编译器和处理器常常会对指令做重排,⼀般分以下三种:
- 单线程环境⾥⾯确保程序最终执⾏结果和代码顺序执⾏的结果⼀致;
- 处理器在进⾏重排序时必须要考虑指令之间的数据依赖性;
- 多线程环境中线程交替执⾏,由于编译器优化重排的存在,两个线程中使⽤的变量能否保证⼀致 性是⽆法确定的,结果⽆法预测。
volatile可以保证有序性,也就是防⽌指令重排序。
所谓指令重排序,就是出于优化考虑,CPU执⾏指令的顺序跟程序员⾃⼰编写的顺序不⼀致。就好⽐⼀ 份试卷,题号是⽼师规定的,是程序员规定的,但是考⽣(CPU)可以先做选择,也可以先做填空。
int x = 11; //语句1
int y = 12; //语句2
x = x + 5; //语句3
y = x * x; //语句4
以上例⼦,可能出现的执⾏顺序有1234、2134、1342,这三个都没有问题,最终结果都是x = 16, y=256。
但是如果是4开头,就有问题了,y=0。
这个时候就不需要指令重排序。
观看下⾯代码,在多线程场景下,说出最终值a的结果是多少? 5或者6
我们采⽤ volatile 可实现禁⽌指令重排优化,从⽽避免多线程环境下程序出现乱序执⾏的现象
package thread;
public class ResortSeqDemo {
int a=0;
boolean flag=false;
/*
多线程下flag=true可能先执⾏,还没⾛到a=1就被挂起。
其它线程进⼊method02的判断,修改a的值=5,⽽不是6。
*/
public void method01(){
// 下面两条指令的执行顺序可能是颠倒的
a=1;
flag=true;
}
public void method02(){
if (flag){
a+=5;
System.out.println("*****最终值a: " + a);
}
}
public static void main(String[] args) {
ResortSeqDemo resortSeq = new ResortSeqDemo();
new Thread(()->{
resortSeq.method01();
},"ThreadA").start();
new Thread(()->{
resortSeq.method02();
},"ThreadB").start();
}
}
package thread;
import java.util.concurrent.TimeUnit;
public class ResortSeqDemo2 {
int a=0;
boolean flag=false;
/*
多线程下flag=true可能先执⾏,还没⾛到a=1就被挂起。
其它线程进⼊method02的判断,修改a的值=5,⽽不是6。
*/
public void method01(){
flag=true;
try{
TimeUnit.SECONDS.sleep(1);
}
catch (InterruptedException e){
e.printStackTrace();
}
a=1;
}
public void method02(){
if (flag){
a+=5;
System.out.println("*****最终值a: " + a);
}
}
public static void main(String[] args) {
ResortSeqDemo2 resortSeq = new ResortSeqDemo2();
new Thread(()->{
resortSeq.method01();
},"ThreadA").start();
new Thread(()->{
resortSeq.method02();
},"ThreadB").start();
}
}
package thread;
import java.util.concurrent.TimeUnit;
public class ResortSeqDemo3 {
volatile int a=0;
boolean flag=false;
/*
多线程下flag=true可能先执⾏,还没⾛到a=1就被 挂起。
其它线程进⼊method02的判断,修改a的值=5,⽽不是6。
*/
public void method01(){
flag=true;
// code1
// code2
try{
TimeUnit.SECONDS.sleep(1);
}
catch (InterruptedException e){
e.printStackTrace();
}
// volatile变量之前的代码都可以重组,以上代码执行完,才会执行a=1
a=1;
// volatile变量之后的代码都可以重组,a=1执行完,才会执行下面的代码
// code3
// code4
}
public void method02(){
if (flag){
a+=5;
System.out.println("*****最终值a: " + a);
}
}
public static void main(String[] args) {
ResortSeqDemo3 resortSeq = new ResortSeqDemo3();
new Thread(()->{
resortSeq.method01();
},"ThreadA").start();
new Thread(()->{
resortSeq.method02();
},"ThreadB").start();
}
}
二、为什么volatile 可实现禁⽌指令重排优化,从⽽避免多线程环境下程序出现乱序执 ⾏的现象?
说说它的原理
我们先来了解⼀个概念,内存屏障(Memory Barrier)⼜称内存栅栏,是⼀个CPU指令,volatile底层 就是⽤CPU的内存屏障(Memory Barrier)指令来实现的,它有两个作⽤
- ⼀个是保证特定操作的顺序性
- ⼆是保证变量的可⻅性。
由于编译器和处理器都能够执⾏指令重排优化。
所以,如果在指令间插⼊⼀条Memory Barrier则会告诉编译器和CPU,不管什么指令都不能和这条Memory Barrier指令重排序,也就是说通过插⼊内存屏障 可以禁⽌在内存屏障前后的指令进⾏重排序优化。
内存屏障另外⼀个作⽤是强制刷出各种CPU的缓存数 据,因此任何CPU上的线程都能读到这些数据的最新版本。