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

时间:2017-12-14 15:18:59

标签: c# multithreading

我已经阅读了Jon的Skeet在线页面,了解如何在C#中创建线程安全的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;
        }
    }
}

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

  

如前所述,上述内容不是线程安全的。两个不同   线程可以评估测试if(instance == null)和   发现它是真的,然后都创建实例,这违反了   单身模式。请注意,实际上该实例可能已经存在   在计算表达式之前创建,但是内存模型   不保证实例的新值将被其他人看到   线程,除非已经通过了合适的内存屏障。

你能否解释为什么内存模型不能保证其他线程不会看到实例的新值?

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

3 个答案:

答案 0 :(得分:14)

  

你能否解释为什么内存模型不能保证其他线程不会看到实例的新值?

内存模型很复杂,目前还没有非常明确的文档记录,但从根本上来说,很少有这样的情况可以安全地依赖于一个线程在另一个线程上“看到”而没有某些锁定或其他线程所写的值。线程间通信正在进行中。

例如,考虑一下:

// 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,则无法保证循环方法永远终止。

现代CPU中有很多级别的缓存,并且要求每次读取都返回主内存会消除大量优化。因此,我们有内存模型,可以保证哪些更改肯定在什么情况下可见。除了这些保证之外,允许JIT编译器假设实际上只有一个线程 - 因此它可以将字段的值缓存在寄存器中,并且永远不会再次访问主存储器,例如。

当前记录的内存模型严重不足,并且建议一些明显奇怪的优化应该是有效的。我不会在那条路上远远地去 ,但是值得在CLR 2.0 memory model阅读Joe Duffy的博客文章。 (这比记录的ECMA内存模型更强大,但是博客文章并不是这类关键文档的理想位置,我认为仍然需要更清晰。)

  

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

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

答案 1 :(得分:4)

  

你能否解释为什么内存模型不能保证其他线程不会看到实例的新值?

该代码存在一些问题。因为还没有为变量分配新值。在比较null(if (instance==null))和分配新值(instance = new Singleton();)之间可能会发生一些事情。

您引用的问题是处理器缓存的内存,变量仍然是null,但已经通过代码赋值在内存中设置。它稍后会更新缓存的内存。

答案 2 :(得分:1)

  

你能解释一下为什么......

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

在多处理器计算机的不同处理器上运行的线程之间实现高效可靠的通信和内存共享对于计算机系统设计者来说是一个具有挑战性的问题。有几种不同的方法,在其中一些方法中,当一个处理器上运行的线程更新共享变量时,在不同处理器上运行的线程可能从不看到更新,除非两个处理器都采取特殊步骤确保共享新值。

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

不幸的是,JVM无法始终知道共享哪些变量以及何时共享它们。所以有些责任落在你身上。谷歌的“Java内存模型”,以确切了解Java程序员的责任。