synchronized能不能禁止指令重排序

首先一定要明确:指令重排序和有序性是不一样的。这一点非常重要。

我们经常都会这么说:
①、volatile能保证内存可见性、禁止指令重排序但是不能保证原子性。
②、synchronized能保证原子性、可见性和有序性
注意:这里的有序性并不是代表能禁止指令重排序。

下面看一个非常典型的禁止重排优化的例子DCL,如下:
在双重检查的单例模式中,既然已经加了synchronized为什么还需要volatile去修饰变量呢?如果synchronized能禁止指令重排,那么完全可以不用要volatile。

package com.cctv;

public class DoubleCheckLock {
    private static DoubleCheckLock instance;

    private DoubleCheckLock() {
    }

    public static DoubleCheckLock getInstance() {
        //第一次检测
        if (instance == null) {
        //同步
            synchronized (DoubleCheckLock.class) {
                if (instance == null) {
                    //多线程环境下可能会出现问题的地方
                    instance = new DoubleCheckLock();
                }
            }
        }
        return instance;
    }
}

上述代码一个经典的单例的双重检测的代码,这段代码在单线程环境下并没有什么问题,但如果在多线程环境下就可以出现线程安全问题。
原因在于某一个线程执行到第一次检测,读取到的instance不为null时,instance的引用对象可能没有完成初始化。
因为instance = new DoubleCheckLock();这句代码并不是一个原子操作,他分为三步(伪代码)

memory = allocate();//1.分配对象内存空间
instance(memory);//2.初始化对象
instance = memory;//3.设置instance指向刚分配的内存地址,此时instance!=null

由于步骤2和步骤3间可能会重排序,如下:

memory = allocate();//1.分配对象内存空间
instance = memory;//3.设置instance指向刚分配的内存地址,此时instance!=null
instance(memory);//2.初始化对象

由于步骤2和步骤3不存在数据依赖关系,而且无论重排前还是重排后程序的执行结果在单线程中并没有改变,因此这种重排优化是允许的。
但是指令重排只会保证串行语义的执行的一致性(单线程),但并不会关心多线程间的语义一致性。所以当一条线程访问instance不为null时,由于instance实例未必已初始化完成,也就造成了线程安全问题。
那么该如何 解决呢,很简单,我们使用volatile禁止instance变量被执行指令重排优化即可

//禁止指令重排优化
private volatile static DoubleCheckLock instance;