一、先看一个程序
public static void main(String[] args) {
Object o = new Object();
}
看他的字节码
0 new #2 <java/lang/Object>
3 dup // 先忽略
4 invokespecial #1 <java/lang/Object.<init> : ()V>
7 astore_1
8 return
可以看到,new一个对象分3个步骤:
0 new #2 <java/lang/Object>
–申请栈空间
4 invokespecial #1 <java/lang/Object. : ()V>
–调用构造方法初始化对象
7 astore_1
–把栈空间和初始化的对象绑定
二、指令重排序
在cpu层面,第二部初始化和第三步绑定是互换顺序的。这称为指令重排序。
- 指令重排序的目的
就是为了更快,提升cpu性能。 - 有序性问题的产生就是源于指令重排序
三、示例代码
- 第一个程序,this溢出
初始化和绑定发生指令重排序,然后有可能输出了p=0(反正我没试过。。),这也称为this溢出。因此不要在构造方法内创建线程,否则容易产生this溢出。
public class T_Escape {
private int p=8;
public T_Escape() {
// 有可能输出中间状态-0
new Thread(()->{System.out.println(this.p);}).start();
}
public static void main(String[] args) {
new T_Escape();
}
}
- 第二个程序,指令重排。
public class OrderingDemo {
static int x=0,y=0;
static int a=0,b=0;
static int count;
public static void main(String[] args) throws InterruptedException {
while(true) {
x=0;
y=0;
a=0;
b=0;
count++;
CountDownLatch latch = new CountDownLatch(2);
Thread t1 = new Thread(() -> {
a = 1;
x = b;
latch.countDown();
});
Thread t2 = new Thread(() -> {
b = 1;
y = b;
latch.countDown();
});
t1.start();
t2.start();
latch.await();
if(x==0 && y==0){
break;
}
}
System.out.println("第"+count+"次"+"发生指令重排序");
}
}
由程序可知,无论t1先执行还是t2先执行,都不可能出现x=0又y=0的情况,除非发生了指令重排序(下面那行先执行x = b)。cpu只保证单线程的数据安全,是有可能发生指令重排序的。(笔者运行了半天也不行,可能是M1处理器没有指令重排序)
四、出现有序性问题时如何解决
使用volatile关键字修饰变量
volatile第二个作用:禁止指令重排序