首先:
同样,当使用 synchronized 关键字时,代码重新排序永远不会按照 JMM 规范发生。
上述说法并不完全准确。JMM 定义了happens-before 关系。JLS 只定义了程序顺序和发生前的顺序。见17.4.5。发生在订单之前。
它对指令的重新排序有影响。例如,
int x = 1;
synch(obj) {
y = 2;
}
int z = 3;
现在对于上面的代码,可以进行以下类型的重新排序。
synch(obj) {
int x = 1;
y = 2;
int z = 3;
}
以上是有效的重新排序。
请参阅Roach Motels 和 Java 内存模型。
synch(obj) {
int z = 3;
y = 2;
int x = 1;
}
以上也是有效的重新排序。
不可能的是 y=2 只会在获得锁之后和释放锁之前执行,这是 JMM 所保证的。此外,为了从另一个线程看到正确的效果,我们只需要在同步块内访问 y 。
现在我来到 DCL。
请参阅 DCL 的代码。
if (singleton == null)
synch(obj) {
if(singleton == null) {
singleton == new Singleton()
}
}
return singleton;
现在上述方法的问题是:
singleton = new Singleton()
不是一条指令。而是一套指令。在完全初始化构造函数之前,很可能首先为单例引用分配对象引用。
因此,如果1发生,那么其他线程很可能将单例引用读取为非 null,因此看到的是部分构造的对象。
可以通过将单例设置为 volatile 来控制上述影响,这也建立了先发生的保证和可见性。