线程之间不共享静态实例

时间:2015-01-08 09:47:18

标签: c# .net multithreading thread-safety

我正在使用一个单例类,假设有一个静态实例,如下所示:

    private static ISingletonClass _instance = null;

    public static ISingletonClass GetInstance(string id = null)
    {
        if (_instance == null)
        {
            if (id != null)
            {
                _instance = new SingletonClass(id);
            }
            else
            {
                throw new NullReferenceException("id is missing!");
            }
        }

        if (id != null && _instance.Id != id)
        {
            _instance = new SingletonClass(id); // changing instance
        }

        return _instance;
    }

该类中的所有其他代码都不是静态的(包括Id属性)。 在运行的早期,当还有一个单独的线程时,我用一些id初始化单例,如下所示:

SingletonClass.GetInstance(<some_not_null_id>);

_instance设置为not null(选中它)。 后来我创建了一些线程来完成一些任务,除此之外,还需要从SingletonClass中读取信息(无需写入)。 根据我发现的任何文档和StackOverflow中的答案,所有线程都应该可以使用相同的实例(我没有使用[ThreadStatic]或任何其他类似的机制)。

但是,当尝试从线程内部没有参数的GetInstance()时,我得到NullException(_instance成员为Null)。

我正在使用.NET 4.5版,并使用VS2012。

有什么想法吗?

2 个答案:

答案 0 :(得分:2)

首先,您对抛出的异常的假设是错误的:

  

NullException(_instance成员为Null)。

NullReferenceException方法中抛出的唯一GetInstance()就是你自己抛出的那个。假设您在其他地方没有将_instance值重置为null的其他代码,则该方法取消引用_instance值的唯一位置是在未确保{{1}的情况下无法访问的语句初始化为某个非空值。

至于更广泛的问题,恕我直言,最大的问题是你的问题定得不明确,实施方面也很糟糕。

即使忽略了“单身人士”(它不是真正的单身人士,但为了争论而让它现在称之为)的问题一直在改变,初始化也不是线程安全的。您有以下潜在的竞争(假设单个CPU核心使插图变得简单):

_instance

正如您在上面的示例中所看到的,现在编写代码的方式,每个线程可以独立地尝试检索实例,将当前值看作Thread 1 Thread 2 -------- -------- call GetInstance() if (_instance == null) --> preempted <-- call GetInstance() if (_instance == null) ... _instance = new SingletonClass(id); ... return _instance; --> preempted <-- if (_instance == null) ... _instance = new SingletonClass(id); ... return _instance; ,并将创建一个新的实例对象被退回。

对于真正的单身人士,实现这一目标的最佳方式是使用null类:

Lazy<T>

在这种情况下,private static readonly Lazy<SingletonClass> _instance = new Lazy<SingletonClass>(() => new SingletonClass()); public static SingletonClass Instance { get { return _instance.Value; } } 类处理所有初始化工作,包括确保以线程安全的方式完成。

在您的情况下,如果您没有真正的单身人士,则上述情况不起作用。 Lazy<T>模式仅在某些内容初始化时才有效,但您希望能够即时更改它。鉴于此,你需要更像这样的东西:

Lazy<T>

以上将确保防止线程同时初始化字段。它们只能竞争锁定,然后保证一个线程是唯一一个初始化对象的线程,假设每个线程为private static ISingletonClass _instance = null; private static readonly object _lock = new object(); public static ISingletonClass GetInstance(string id = null) { lock (_object) { if (_instance == null || (id != null && _instance.Id != id)) { if (id == null) { throw new ArgumentNullException("id"); } _instance = new SingletonClass(id); } return _instance; } } 传递相同的值。

也就是说,这只能修复代码中的基本线程安全问题。还有一些更大的问题。

首先,如果一个线程检索当前实例,那么您希望代码做什么,然后其他一些线程在使用它检索的实例完成第一个线程之前更改当前实例?我并不是说这本身就是破碎的,但它至少非常脆弱,你绝对需要考虑这种情况,并自己决定在这种情况下应该采取什么样的行为。

其次,这是单身模式的一个非常脆弱的变异。一个真正的单例将有一个对象,在进程的生命周期中只分配一次。这确保了设计的简单性和行为的可预测性。

您所拥有的实现,必然会让您更难理解代码在任何给定点上所做的事情。实际上,当某些错误出现并且他们试图追踪错误的实际原因时,它会为某些开发人员当天(无论是您自己还是其他人)增加大量时间。

此类的实例与字符串ID绑定的事实表明,更好的方法可能是维护id个对象,要求所有调用者始终指定ID,并使用该ID来检索(当然可能是懒惰地)当前线程需要的对象。

我强烈推荐不同的设计。但至少,如果您决定必须朝这个方向发展,请确保您已经考虑了线程事件的所有各种组合,并且不仅确定了每个给定方案中的正确行为,还添加了代码以确保任何假设约束

答案 1 :(得分:0)

我相信你想要一个真正的单身人士,你可以更新一个值。此更新需要线程安全。创作应该是一个单独的方法。

private static readonly MyContainerClass _instance = new MyContainerClass(); //true singleton

private sealed class MyContainerClass //this is the singleton
{
   private ISingletonClass _value = null;

   public ISingletonClass Value //threadsafe access to your object
   {
      get { lock(this){ return _value; } }
   }

   public ISingletonClass CreateOrUpdateValue(string id) //threadsafe updating of your object
   {
      if (id==null) throw new ArgumentNullException("id is missing!");
      lock(this)
      {
        var instance = _instance.Value;

        if (instance == null || instance.Id != id)
          _instance.Value = new SingletonClass(id);

        return _instance.Value;
       }
    }
}

public static void CreateOrUpdateInstance(string id)
{
    _instance.CreateOrUpdateValue(id);
}

public static ISingletonClass GetInstance()
{
    var instance = _instance.Value;

    if (instance == null)
       throw new Exception("Instance has not been created");

    return _instance;
}

// this is like your original method if you really want it
public static ISingletonClass GetInstance(string id)
{
    return _instance.CreateOrUpdateValue(id);
}