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

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

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 synchronisation blockabcInstance = new ABC();原子的,如果不是,则这种创建单例的方法是错误的。

在C ++中,abcInstance = new ABC();大致由三个指令组成:


创建ABC对象。
为ABC分配内存。
将其分配给abcInstance。


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

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

最佳答案

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


  是if(abcInstance == null)abcInstance = new ABC();是原子的,如果不是,则这种创建单例的方法是错误的。


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

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


在Java 5之前,内存模型没有为该实现可靠地提供足够的内存可见性保证。
在Java 5(和更高版本)的内存模型中,一个线程对volatile变量的写入与另一个线程对变量的后续读取之间的关系发生了。这表示:


如果第一个线程已写入第二个线程,则保证第二个线程看到abcInstance的非空值。
发生在关系之前还可以确保第二个线程将看到第一个线程创建的ABC实例的完全初始化状态。
synchronized块可确保一次只能创建一个ABC实例。



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


The "Double-Checked Locking is Broken" Declaration




如安德鲁·特纳(Andrew Turner)所述,在Java中有一种更简单,更干净的方法来实现单例类:使用enum


Implementing Singleton with an Enum (in Java)

09-26 06:36