在 java 的单例双空检查实现中使用 volatile

Use of volatile in singleton double null check implementation in java

我想知道在 Singleton 的双空检查实现中将 Instance 变量设置为 volatile 有什么用。因为根据我的理解,同步块提供之前隐式发生。没有两个线程可以同时访问该同步块,并且在退出同步块时,线程将其所有本地缓存​​数据写入主内存。 我搜索了很多,但我仍然对这个实现有疑问。请解释正确的用法。

private volatile Singleton INSTANCE; 
public Singleton get() { 
    if (INSTANCE == null) { 
        synchronized (this) { 
            if (INSTANCE == null) { 
                INSTANCE = new Singleton(); 
            } 
        } 
    } 
    return INSTANCE; 
}

在上面的双重检查代码中。如果我不让我的实例变得不稳定,那可能是什么问题。因为当第一个线程进入同步块时,没有其他线程可以访问该块。当第一个线程离开那个块时。对象将被创建,并且由于发生在 属性 之前,实例的最新值将被写入主内存。那么为什么 volatile 在这种情况下很重要?

这里有一个 link (Is there any need to add volatile keyword to guarantee thread-safe singleton class in java?) 的类似问题。但答案不是很清楚。 我认为在上面给定的场景中,任何类型的 JVM 优化都不会导致中断发生在同步块

之前

Because as per my understanding synchronize block provide happens before implicitly. No two threads can access that synchronized block concurrently and while exiting from synchronized block

如果你有一个简单的惰性单例实现,这是正确的。因为对变量的每次访问都经过同步块。

private Singleton INSTANCE;

public static synchronized Singleton getInstance() {
  if (INSTANCE == null) {
    INSTANCE = new Singleton();
  }
  return INSTANCE;
}

但是一旦你有了一个安全的 double-check 惰性实现,并不是每个代码路径都经过同步块。第二次调用此方法将从本地字段读取变量,而无需进入 synchronized 块,并且 INSTANCE 必须是可变的。

private volatile Singleton INSTANCE;

public Singleton get() {
  if (INSTANCE == null) {
    synchronized (this) {
      if (INSTANCE == null) {
        INSTANCE = new Singleton();
      }
    }
  }
  return INSTANCE;
}

您可以在 this article 中阅读有关安全发布的更多信息。

volatile 在这里很重要,因为字节码级别的 JVM 内部结构。声明

INSTANCE = new Singleton()

在字节码级别实际上是这样的:

1. INSTANCE = create Singleton object
2. call constructor of INSTANCE

如果 INSTANCE 是 volatile,JVM 保证从其他线程的角度来看 1+2 作为原子操作执行,例如通过在将实例存储在字段中之前将其存储在堆栈中。如果它是 not volatile 则可能是其他线程在调用构造函数之前已经可以 看到 对 Singleton 的引用。因此这些线程可能会看到一个不完整的对象。