有序性
java程序会被编译成一条条指令,cpu在执行这些指令时,为了提高执行效率,会对指令进行重排序
常见的指令重排序问题:
在懒汉单例模式中,创建对象分为三步:分配空间、对象初始化、构建对象引用。由于synchronized无法保证有序性,所以可能出现这样一种情况:创建对象指令重排序,变成分配空间、构建对象引用、对象初始化。如此一来,没有获取锁的线程判断对象变量不为空后,就将对象引用返回了,这是有问题的,所以对象变量需要用volatile修饰
public class test {
private volatile static test t;
private test() {
}
public static test getInstance() {
if (null == t) {
synchronized (test.class) {
if (null == t) {
t = new test();
}
}
}
return t;
}
}
as-if-serial
as-if-serial语义:
- cpu层面的指令重排规则
- 无论指令如何重排,在单线程执行的情况下,指令执行的最终结果是不变的
- 如果变量之间存在依赖关系,那么不允许做指令重排
happens-before
happens-before是JVM层面的指令重排规则:
- 单线程happen-before原则:在同一个线程中,书写在前面的操作happen-before后面的操作
- 锁的happen-before原则:同一个锁的unlock操作happen-before此锁的lock操作
- volatile的happen-before原则:对一个volatile变量的写操作happen-before对此变量的任意操作(当然也包括写操作了)
- happen-before的传递性原则:如果A操作happen-before B操作,B操作happen-before C操作,那么A操作happen-before C 操作
- 线程启动的happen-before原则:同一个线程的start方法happen-before此线程的其他方法
- 线程中断的happen-before原则:对县城interrupt方法的调用happen-before被中断线程的检测到中断发送的代码
- 线程终结的happen-before原则:线程中的所有操作都happen-before线程的终止检测
- 对象创建的happen-before原则:对象的初始化完成先于他的finalize方法的调用
编写的代码如果不满足上面的某一规则,有可能触发指令重排序。
不用过分关注happens-before,只要保证编写的代码并发安全
保证有序性
volatile关键字
除了满足happens-before8条规则外,还可以使用volatile保证有序性
volatile会在变量操作的前后加上内存屏障,对于volatile前后无依赖关系的指令可以随便怎么排序
例如:
int x=1;
int y=2;
volatile int z=3;
x++;
y--;
在语句volatile int z=3之前,先执行x的定义还是y的定义都无所谓,cpu可以随意排序,只要能够保证在执行到z=3的时候,x=1,y=2就行。同理,关于x的自增和y的自减,cpu也可以随意排序。但是,语句volatile int z=3之前的指令不能跑到该语句后面去执行,该语句后面的指令也不能跑到前面去执行。
synchronized和lock保证了代码块的原子性,也就保证了代码块与代码块之间执行的有序性。但是代码块中的指令是可以被重排序的。