java - 双重检查创建单例问题的方式

标签 java c++ singleton

如何读取(在关键资源 block 外)和写入(在关键资源 block 内)没有原子性问题。

我已经阅读并与不同的人讨论过,但大多数人没有回答这两个操作是否都是原子操作以及如何针对上述问题实际实现原子性。

class ABC {
    private static volatile ABC abcInstance;
    static ABC getInstance(){
        if(abcInstance == null){
            synchronized(ABC.class){
                if(abcInstance == null){
                    abcInstance = new ABC();
                    return abcInstance;
                }
            }
        }
        return abcInstance;
    }

}

if(abcInstance == null) outside synchronization blockabcInstance = new ABC(); 是原子的,如果不是那么这种创建单例的方式是错误的。

在 C++ 中,abcInstance = new ABC(); 从广义上讲包含三个指令:

  1. 创建 ABC 对象。
  2. 为 ABC 分配内存。
  3. 将其分配给 abcInstance。

为了优化,编译器可以以任何方式重新排列这三个指令。假设它遵循 2->3->1 并且在指令 3 中断发生后,下一个调用 getInstance() 的线程将读取 abcInstance 有一些值,然后它将指向没有 ABC 对象的东西。

如果 C++ 和 Java 都错了,请指正。

最佳答案

这仅回答了您问题的 Java 部分。

Is if(abcInstance == null) and abcInstance = new ABC(); are atomic, if not then this way of creating singleton is wrong.

(潜在的)问题不是原子性。 (从执行分配的线程和读取分配变量的线程的角度来看,引用分配是原子的。)

问题是当写入 abcInstance 的值对另一个线程可见时。

  • 在 Java 5 之前,内存模型无法为该实现的可靠工作提供足够的内存可见性保证。

  • 在 Java 5(及更高版本)内存模型中,一个线程对 volatile 变量的写入与另一个线程的后续读取之间存在发生在之间的关系的变量。这意味着:

    1. 如果第一个线程写入了 abcInstance 的非空值,则第二个线程保证看到它。
    2. happens before 关系还保证第二个线程将看到第一个线程创建的 ABC 实例的完全初始化状态。
    3. synchronized block 确保一次只能创建一个 ABC 实例。

这是解释为什么旧的双重检查锁定实现被破坏的权威文章:


正如 Andrew Turner 所说,有一种在 Java 中实现单例类的更简单、更清晰的方法:使用 enum

关于java - 双重检查创建单例问题的方式,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/57888409/

相关文章:

java - 在java中不使用私有(private)引用实现单例模式?

Java 和空格作为语法(ala Python)?

java - 如何从 Struts 2 中的选定对象获取对象属性

java - 为什么最终静态变量不能在实例 block 中赋值?

c++ - 如何将一个QMap放入另一个QMap

java - 访问 SQL 数据库时避免列名冗余?

c++ - 并行运行多个 QEventLoop(对于 QtNetwork)

c++ - 通过weak_ptr创建单例类是否正确

c++ - 为了在测试中调用析构函数(用于测试),将基类静态转换为派生类有多可怕?

java - 预期为BEGIN_OBJECT,但在第1行第6列处为STRING