[英]static instance is not shared between threads
我正在使用一个单例类,该类应该具有一个静态实例,如下所示:
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设置为不为null(选中)。 稍后,我创建一些线程来执行某些任务,这些任务需要从SingletonClass中读取信息(无需编写)。 根据我发现的任何文档以及StackOverflow中的答案,所有线程都应使用相同的实例(我没有使用[ThreadStatic]或任何其他类似的机制)。
但是,当尝试从线程内部不带任何参数的GetInstance()时,我得到NullException(_instance成员为Null)。
我正在使用.NET 4.5版,并使用VS2012。
有任何想法吗?
首先,您对引发异常的假设是不正确的:
NullException(_instance成员为Null)。
从GetInstance()
方法中引发的唯一NullReferenceException
是您自己引发的异常。 假设您没有其他地方可以将_instance
值重置为null
,则该方法取消引用_instance
值的唯一位置是在未确保_instance
初始化为某些非空值的情况下无法到达的语句。
至于更广泛的问题,恕我直言,最大的问题是您定义的问题不明确,并且实现不完善。
即使忽略“单身人士”(实际上不是单身人士,但为了争论起见,我们暂时称之为“单身人士”)是否曾经更改的问题,初始化也不是线程安全的。 您具有以下潜在的竞争(假设使用单个CPU内核可以简化图示):
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的事实表明,更好的方法可能是维护Dictionary<string, SingletonClass>
对象,要求所有调用者始终指定ID,并使用该ID进行检索(可能是初始化当然是懒惰的)当时线程需要的对象。
我强烈建议您使用其他设计。 但是至少,如果您决定必须朝这个方向发展,请确保已考虑了线程事件的所有各种组合,不仅确定了每种给定方案的正确行为,而且还添加了代码以确保所有假设约束。
我相信您想要一个具有可更新值的真正单身人士。 此更新必须是线程安全的。 创建应该是与获取不同的方法。
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);
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.