为什么这个单例构造代码不是线程安全的?

时间:2010-09-21 05:49:11

标签: design-patterns thread-safety

在这段代码中,为什么写“非线程安全”? 谢谢

class Singleton
{
  private static Singleton _instance;

  // Constructor is 'protected'
  protected Singleton()
  {
  }

  public static Singleton Instance()
  {
    // Uses lazy initialization.
    // **Note: this is not thread safe.**
    if (_instance == null)
    {
      _instance = new Singleton();
    }

    return _instance;
  }
}

9 个答案:

答案 0 :(得分:24)

如果两个线程在没有创建单例实例的情况下同时运行if (_instance == null)检查,则它们都会尝试调用new来创建单例实例并将对它们的引用存储到同一个实例中变量

由于它们将尝试存储的引用将在线程之间共享,因此该操作不是线程安全的。也可能发生创建单个类的两个实例将破坏程序。

答案 1 :(得分:8)

因为Singleton不提供_instance属性的互斥。

使用锁定来实现线程安全:

Object thisLock = new Object();

public static Singleton Instance()
{
    lock (thisLock)
    {
        if (_instance == null)
        {
           _instance = new Singleton();
        }
    }

    return _instance;
}

这个例子是C# - 我不知道你正在使用哪种编程语言。

http://msdn.microsoft.com/en-us/library/c5kehkcz(VS.90).aspx

答案 2 :(得分:5)

基于RPM1984的回答:

我使用以下锁定对象: object thisLock = typeof( Sinlgeton );

或只是

...
lock( typeof( Singleton ) )
{
   ...
}

你们之间的表现:

public Singleton getInstance()
{
    // the first query may save a performance-wise expensive lock - operation
    if ( null == _instance )
    {
       lock ( typeof( Singleton ) )
       {
          if ( null == _instance )
          {
             _ instance = new Singleton()
          }
       }
    }

    return _instance;
}

BTW:这称为双锁单例模式。

答案 3 :(得分:3)

因为在这种情况下可能会创建单个的多个实例。假设两个线程已进入Instance方法并且尚未创建单个对象(即_instance为NULL)。假设第一个线程执行if条件并输入它。但在此之前,new线程上下文切换发生,第二个线程开始执行。它还测试if条件并发现它为NULL并执行new。现在,第一个线程开始执行并创建对象的另一个实例

答案 4 :(得分:1)

这比我最初的想法更令人兴奋:

所以最好的解决方案是

public sealed class Singleton
{
    private static readonly Singletion _instance = new Singleton();

    private Singleton()
    {
       //do your construction
    }

    public static Singleton getInstance()
    {
       return _instance;
    }
}

根据我目前的理解,编程环境(Java,.NET)对于此解决方案无关紧要。

有任何想法或意见吗?

进一步阅读我已经挖出来了:

编辑: 对于Java,它也应该有效:

现在,如果有人指出一个保存C ++版本,它将是完整的...(我远离C ++太长时间不记得细节......)

答案 5 :(得分:0)

我认为在Java中只需向getInstance方法添加一个synchronized标志就足够了。这将阻止其他线程进入该方法而另一个进入该方法。

答案 6 :(得分:0)

最大的问题是检查实例是否已存在且其延迟初始化不是原子操作。

最简单的例子是线程A检查条件然后产生线程B;线程B检查相同的条件并创建一个新对象并返回该新对象;线程B然后返回到线程A,线程A从它停止的地方开始;线程A然后去创建一个新对象并返回它。

还有其他一些值得注意的问题:

  1. _singleton变量应标记为volatile,以确保正确发布写入。
  2. 该课程应标记为最终或密封以防止子类出现问题。
  3. 在Java中,您应该重写clone()方法以抛出UnsupportedOperationException。
  4. 如果出于某种原因需要使类可序列化,则还需要提供一个实现来处理该场景(否则客户端可以连续反序列化流以创建多个实例)。

答案 7 :(得分:0)

如果单例创建起来很便宜,并且主要要求是应用程序中的所有内容都看到相同的内容,则另一种方法是:

  If TheSingleton Is Nothing Then
    Dim newSingleton As New Singleton
    If Interlocked.CompareExchange(TheSingleton, newSingleton, Nothing) IsNot Nothing Then
      newSingleton.Dispose  ' If applicable
    End If
  End If

如果两个线程在执行CompareExchange之前通过“If”测试,则将创建两个单例。但是,只有传递给CompareExchange的第一个单例才能用于任何事情;另一个将被丢弃。

答案 8 :(得分:0)

实际上,的示例可能是类型安全的。此外,它甚至可能是一个相当不错的主意。

如果多个线程在第一个线程写入_instance之前遇到空检查(实际上,可能是在第一个线程写入_instance之后,但在第二个线程的CPU将新值加载到其缓存之前),那么第二个(以及第三个和第四个......)线程将创建一个新实例并将其写入_instance。

在垃圾收集语言中,这只是意味着在短时间内多个线程将拥有自己的_instance版本,但很快它们就会退出范围并有资格进行垃圾收集。

现在,这是浪费,但它实际上是否存在问题取决于创建新实例的成本是多少以及是否存在多个负面后果。这种不必要的实例对象重复的缺点往往很小,在这种情况下它可能比锁定成本更少负(锁定相对便宜但不是免费的,等待锁定可能相当昂贵,并且有些情况[死锁是最极端的情况]瘫痪很贵),甚至是CASsing。即使它更贵,它实际上可能仍然不安全。

由于我们无法判断上述示例中是否是这种情况,我们实际上并不知道它是否是线程安全的。

最有可能的是,创建静态构造是可行的方法。