2011-05-13 82 views
8

这不是一个简单而且安全(因此更好)的方式来实现一个单例,而不是做双重检查的锁定曼波 - 曼波吗?这种方法的任何缺点?Simpleton的简单实现


public class Singleton 
{ 
    private static Singleton _instance; 
    private Singleton() { Console.WriteLine("Instance created"); } 

    public static Singleton Instance 
    { 
     get 
     { 
      if (_instance == null) 
      { 
       Interlocked.CompareExchange(ref _instance, new Singleton(), null); 
      } 
      return _instance; 
     } 
    } 
    public void DoStuff() { } 
} 

编辑:测试线程安全的失败,任何人都可以解释,为什么? Interlocked.CompareExchange怎么不是真正的原子?


public class Program 
{ 
    static void Main(string[] args) 
    { 
     Parallel.For(0, 1000000, delegate(int i) { Singleton.Instance.DoStuff(); }); 
    } 
} 

Result (4 cores, 4 logical processors) 
Instance created 
Instance created 
Instance created 
Instance created 
Instance created 
+3

难道这个例子仍然会造成一个问题,如果两个线程都能够得到'如果(_instance == NULL)内'检查他们之一执行'Interlocked.CompareExchange'功能? – Tim 2011-05-13 19:23:26

+1

这是不正确的。根据MSDN文档:“比较和交换操作是作为原子操作执行的。” – kateroh 2011-05-13 19:33:48

+0

@ kateroh - 啊,我明白了,我在同一时间将它作为一个比较和交换。所以它是一个双重检查。我的错。 – Tim 2011-05-13 19:39:41

回答

8

您可能要创建多个实例,但这些会得到垃圾回收,因为他们没有任何地方使用。在任何情况下,static _instance字段变量都不会多次更改其值,即它从null变为有效值的单个时间。因此,尽管已创建多个实例,但此代码的使用者只会看到相同的实例。

锁定自由编程

Joe Duffy,在其题为在Windows并发编程的书其实这种分析非常模式,你尝试在第10章,内存模式和锁自由使用,526页

他指的是这种模式作为参考宽松的延迟初始化:

public class LazyInitRelaxedRef<T> where T : class 
{ 
    private volatile T m_value; 
    private Func<T> m_factory; 

    public LazyInitRelaxedRef(Func<T> factory) { m_factory = factory; } 


    public T Value 
    { 
     get 
     { 
      if (m_value == null) 
       Interlocked.CompareExchange(ref m_value, m_factory(), null); 
      return m_value; 
     } 
    } 

    /// <summary> 
    /// An alternative version of the above Value accessor that disposes 
    /// of garbage if it loses the race to publish a new value. (Page 527.) 
    /// </summary> 
    public T ValueWithDisposalOfGarbage 
    { 
     get 
     { 
      if (m_value == null) 
      { 
       T obj = m_factory(); 
       if (Interlocked.CompareExchange(ref m_value, obj, null) != null && obj is IDisposable) 
        ((IDisposable)obj).Dispose(); 
      } 
      return m_value; 
     } 
    } 
} 

正如我们所看到的,在上面的示例方法中,以创建丢弃对象的代价是免费的。在任何情况下,对于这样的API的消费者,Value属性都不会改变。

平衡取舍

锁定自由是有代价的,是仔细选择你的取舍的问题。在这种情况下,锁定自由的代价是你必须创建你不打算使用的对象的实例。这可能是一个可以接受的价格,因为你知道,通过锁定免费,死锁的风险较低,并且还存在线程争用。

在这种特别实例然而,一个单身的语义在本质上创建一个对象的实例,所以我宁愿选择Lazy<T>作为@Centro在他的回答引用了。

尽管如此,它仍然存在问题,当应该我们使用Interlocked.CompareExchange?我喜欢你的榜样,这是相当激动人心的,很多人很快就会错误地将它当作错误,当它不是可怕的错误的@Bindy引号。

这一切都归结到你是否计算了权衡和决定:

  • 有多重要,你产生一个且只有一个实例?
  • 锁定自由有多重要?

只要你意识到权衡,并且有意识地决定创建新的对象以获得无锁的好处,那么你的例子也可以是可接受的答案。

3
public class Singleton 
{ 
    private static Singleton _instance = new Singleton(); 
    private Singleton() {} 

    public static Singleton Instance 
    { 
     get 
     { 
      return _instance; 
     } 
    } 
} 
+0

伟大的思想和所有:) – Blindy 2011-05-13 19:24:14

+0

哪里是静态构造函数? – ZOXEXIVO 2016-08-09 23:04:20

+0

@ZOX - 不需要。 – Oded 2016-08-10 08:25:41

9

如果单是曾经在多次初始化自身的危险,你有很多更严重的问题。为什么不使用:

public class Singleton 
{ 
    private static Singleton instance=new Singleton(); 
    private Singleton() {} 

    public static Singleton Instance{get{return instance;}} 
} 

关于初始化,绝对是线程安全的。

编辑:如果我不清楚,你的代码是可怕的错误if检查和new都是不是线程安全!你需要使用适当的单例类。

+1

这并没有给出与海报代码相同的功能。 OP的代码只在被引用时才返回_instance(所以如果使用,只会初始化它),而无论类是否需要,您的代码都会初始化它。 – Streklin 2011-05-13 19:26:27

+0

@Streklin,我不会对这位先生赌得太多...... – Blindy 2011-05-13 19:27:19

+0

嗯够公平的 - 我不会这么做的。但你介意解释我错过了什么?我没有看到OP的代码如何被初始化,除非在Instance属性上调用get函数。 – Streklin 2011-05-13 19:30:03

3

我不相信你完全可以相信这一点。是的,Interlocked.CompareExchanger是原子的,但新的Singleton()在任何非平凡的情况下都不会是原子的。由于在交换值之前必须进行评估,因此这通常不是线程安全的实现。

+0

更不用说在调用互锁函数之前进行非原子比较... – Blindy 2011-05-13 19:24:49

1

这不是线程安全的。

您需要一个锁来将if()Interlocked.CompareExchange()放在一起,然后您就不再需要CompareExchange了。

1

你仍然有问题,你很可能创建和扔掉你的单身人士的实例。执行Interlocked.CompareExchange()时,无论赋值是否成功,构造函数总是会被执行。所以,你没有更好(或者更糟,恕我直言)比,如果你说:

if (_instance == null) 
{ 
    lock(latch) 
    { 
    _instance = new Singleton() ; 
    } 
} 

更好的性能,面对面的人线程争比,如果你换了lock的位置和测试空,但面临着构建额外实例的风险。

+0

在编辑中看到测试,只有在_instance == null时才创建实例 – kateroh 2011-05-13 20:02:58

+1

多于1个线程可以通过null之前的测试'_instance'被设置。比方说,你可以有4个线程堆栈在比较和交换中。一个会成功,但你的单身人士的4个实例将被创建,并且3个将最终被抛弃。而且......你无法控制哪个**线程会实例化单例。在上下文交换方面,先入先出不保证。欢迎来到模糊多线程/多处理世界。 – 2011-05-13 20:19:09

+0

没错。 +1 – kateroh 2011-05-13 20:27:02

3

这个怎么样?

public sealed class Singleton 
{ 
    Singleton() 
    { 
    } 

    public static Singleton Instance 
    { 
     get 
     { 
      return Nested.instance; 
     } 
    } 

    class Nested 
    { 
     // Explicit static constructor to tell C# compiler 
     // not to mark type as beforefieldinit 
     static Nested() 
     { 
     } 

     internal static readonly Singleton instance = new Singleton(); 
    } 
} 

这是此页面上的第五个版本: http://www.yoda.arachsys.com/csharp/singleton.html

我不知道,但作者似乎认为它的两个线程安全的,延迟加载。

6

为了不使用'双重检查锁定mambo-jambo'或根本不实施自己的单件重新发明轮子,请使用包含在.NET 4.0 - Lazy<T>中的现成解决方案。

+0

谢谢,不知道这个+1。我在2.0虽然:) – kateroh 2011-05-13 21:32:16

1
+0

Centro已经提到了一个可能的实现使用Lazy。不管怎么说,还是要谢谢你! – kateroh 2011-05-16 17:50:39

+0

对不起,我一定错过了它。我实际上使用了一些比较复杂的东西来使_ensure_它的单身用作软件许可证,而Lazy <>非常适合!但它与这个问题没有关系。 – expelledboy 2011-05-17 09:38:35

2

你的单例初始值设定项的行为与它应该完全相同。请参阅Raymond Chen的Lock-free algorithms: The singleton constructor

这是一个双重检查锁,但没有锁定。在完成初始构建时,我们不要考虑锁定,而是让它成为创建对象的免费人员。如果五个线程同时到达此代码,那么确定,我们创建五个对象。在每个人创建他们认为是获胜对象后,他们调用InterlockedCompareExchangePointerRelease尝试更新全局指针。

这种技术适用于让多个线程尝试创建单例(并让所有输家销毁其副本)的情况。如果创建单例代价昂贵或具有不需要的副作用,那么您不希望使用free-for-all算法。

每个线程创建对象;因为它认为没有人创造它。但期间InterlockedCompareExchange,只有一个线程将真的能够全球单身人士。

奖金阅读

0

我觉得.NET 4.0后,最简单的方法是使用System.Lazy<T>

public class Singleton 
{ 
    private static readonly Lazy<Singleton> lazy = new Lazy<Singleton>(() => new Singleton()); 

    public static Singleton Instance { get { return lazy.Value; } } 

    private Singleton() { } 
} 

乔恩斯基特有一个很好的文章here,涵盖了很多实现单的方式和每个人的问题。

+0

这就是Centro已经给出的[相同的答案](http://stackoverflow.com/a/5997760/1838048)。 – Oliver 2015-10-30 13:52:53

1

自动属性初始化(C#6.0)似乎不会导致您看到的单例的多个实例化。

public class Singleton 
{  
    static public Singleton Instance { get; } = new Singleton(); 
    private Singleton(); 
} 
0

请勿使用锁定。用你的语言环境

晴简单的线程安全的实现是:

public class Singleton 
{ 
    private static readonly Singleton _instance; 

    private Singleton() { } 

    static Singleton() 
    { 
     _instance = new Singleton(); 
    } 

    public static Singleton Instance 
    { 
     get { return _instance; } 
    } 
}