五、并发编程三大特性之一:有序性

70 阅读3分钟

有序性

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保证了代码块的原子性,也就保证了代码块与代码块之间执行的有序性。但是代码块中的指令是可以被重排序的。