线程安全Singleton:为什么内存模型不能保证其他线程将看到新实例



我已经在乔恩的气旋在线页面上阅读了有关如何在C#

中创建线程Safe Singleton的信息

http://csharpindepth.com/articles/general/singleton.aspx

// Bad code! Do not use!
public sealed class Singleton
{
    private static Singleton instance=null;
    private Singleton()
    {
    }
    public static Singleton Instance
    {
        get
        {
            if (instance==null)
            {
                instance = new Singleton();
            }
            return instance;
        }
    }
}

在此代码下面的段落中,它说:

如前所述,以上不是线程安全。两个不同 如果(实例== null)和 发现它是正确的,然后都创建了实例,这违反了 单例图案。请注意,实际上该实例可能已经有 是在评估表达式之前创建的,但是记忆模型 不能保证其他实例的新价值将被其他 除非经过合适的内存屏障,否则线程。

您能解释一下为什么内存模型不能保证实例的新值将通过其他线程看到?

静态变量位于堆上,但是为什么它不会立即与其他线程共享?我们是否需要等待上下文开关,以便另一个线程知道该实例不再为空?

您能解释一下为什么内存模型不能保证实例的新值将通过其他线程看到?

内存模型很复杂,目前还没有清楚地记录下来,但是从根本上讲,很少有情况可以安全地依靠一个在没有某个线程上"看到"而没有某个锁定或其他锁定的一个线程所写的值线程间的通信进行。

例如,请考虑以下内容:

// Bad code, do not use
public class BigLoop
{
    private static bool keepRunning = true;
    public void TightLoop()
    {
        while (keepRunning)
        {
        }
    }
    public void Stop()
    {
        keepRunning = false;
    }
}

如果您创建了两个线程,其中一个称为TightLoop,另一个称为Stop,则无法保证循环方法将 em ever 终止。

现代CPU中有很多级别的缓存,并且要求每个读取返回到主内存都可以消除大量优化。因此,我们有记忆模型可以保证在哪种情况下可以看到哪些更改肯定。除保证外,允许JIT编译器实际上只有一个线程 - 因此它可以缓存寄存器中字段的值,例如,再也不会击中主内存。

当前记录的内存模型不足,并且建议 应该有效一些明显的优化。我不会 沿着这条路线,但是值得阅读乔·达菲(Joe Duffy)在CLR 2.0内存模型上的博客文章。(这比已记录的ECMA内存模型要强,但是博客文章并不是这样关键文档的理想场所,我认为仍然需要更加清晰。)

静态变量位于堆上,但是为什么不与其他线程共享?

it IS 与其他线程共享 - 但该值不一定立即可见。

您能解释一下为什么内存模型不能保证实例的新值将通过其他线程看到?

该代码有一些问题。因为尚未分配给变量的新值。比较null(if (instance==null))和新值的分配(instance = new Singleton();)之间可能会发生很多事情。

您引用的问题是关于由处理器缓存的内存,该变量仍然是null,但已经通过代码的分配设置在内存中。

,它将更新该缓存的内存。

您可以解释为什么...

因为它允许JVM实施者为您提供在各种不同计算机架构上获得最佳性能的JVM。

在多处理器计算机的不同处理器上运行的线程之间,有效,可靠的通信和内存共享是计算机系统设计人员的挑战性问题。有几种不同的方法,在其中一些方法中,当在一个处理器上运行的线程更新共享变量时,在其他处理器上运行的线程可能会从不确保共享新值。

那些"特殊步骤"可能是昂贵的。Java编程语言的设计使JVM开发人员有机会在需要时采取特殊步骤,但这并不迫使他们在不需要时采取这些步骤。

不幸的是,JVM不能总是知道正在共享哪些变量以及它们何时共享。因此,某些责任置于您身上。Google用于" Java内存模型",以确切找出Java程序员的职责。

最新更新