代码之家  ›  专栏  ›  技术社区  ›  odiseh

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

  •  5
  • odiseh  · 技术社区  · 14 年前

    谢谢你

    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 回复  |  直到 12 年前
        1
  •  24
  •   sharptooth    14 年前

    如果两个线程运行 if (_instance == null) 同时检查,虽然没有创建单例实例,但它们都将尝试调用 new 创建singleton实例并将对它们的引用存储到同一个变量中。

        2
  •  8
  •   RPM1984    14 年前

    因为Singleton不提供对\u实例属性的互斥。

    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

        3
  •  5
  •   Mario The Spoon    14 年前

    根据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;
    }
    

    顺便说一句:这就是所谓的双锁单例模式。

        4
  •  3
  •   Naveen    14 年前

    因为有可能 倍数 Instance 方法和单例对象尚未创建(即。 _instance 为空)。然后假设第一个线程执行 if new 线程上下文切换发生,第二个线程开始执行。它还测试 如果 新的 . 现在第一个线程开始执行并创建 再举一个例子 对象的名称。

        5
  •  1
  •   Mario The Spoon    14 年前

    所以最好的解决办法是

    public sealed class Singleton
    {
        private static readonly Singletion _instance = new Singleton();
    
        private Singleton()
        {
           //do your construction
        }
    
        public static Singleton getInstance()
        {
           return _instance;
        }
    }
    

    有什么想法或意见吗?

    进一步阅读我发现:

    至于Java,它也应该起作用:

    现在如果有人指出一个保存C++版本,它将是完整的…(我离开C++太长了,无法记住细节……)

        6
  •  0
  •   InsertNickHere    14 年前

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

        7
  •  0
  •   Erik Pilz    14 年前

    最大的问题是,检查实例是否已经存在以及它的延迟初始化不是原子操作。

    最简单的例子是线程A检查条件,然后将其返回给线程B;线程B检查相同的条件,并创建一个新对象并返回该新对象;线程B然后将其返回给线程A,线程A从停止的位置开始;线程A然后继续创建一个新对象并返回该对象。

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

    1. \u singleton变量应标记为volatile,以确保正确发布写入。
    2. 该类应标记为final或sealed,以防止子类出现问题。
    3. 如果出于某种原因需要使类可序列化,那么还需要提供一个实现来处理该场景(否则,客户机可以连续反序列化流以创建多个实例)。
        8
  •  0
  •   supercat    14 年前

      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
    

    如果两个线程在进行比较交换之前通过了“If”测试,那么将创建两个单例。但是,只有传递给CompareExchange的第一个单例将被用于任何事情;另一个将被丢弃。

        9
  •  0
  •   Jon Hanna    14 年前

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

    在垃圾收集语言中,这只是意味着在一段时间内,多个线程将有自己的\u实例版本,但很快它们就会退出作用域并有资格进行垃圾收集。

    现在,这是浪费,但它是否真的是一个问题取决于创建一个新实例的成本有多高,以及存在多个实例是否会产生任何负面后果。通常情况下,这种不必要的实例对象复制的负面影响很小,在这种情况下,它的负面影响可能小于锁定的成本(锁定相对便宜,但不是免费的,等待锁定可能会非常昂贵,在某些情况下[死锁是最极端的情况]会非常昂贵),甚至是CASsing。即使它更贵,它可能仍然不是真正的不安全。