public class JobModel
{
static readonly Object _padlock = new object();
static readonly Dictionary<Tuple<int, string>, JobModel> _jobModelCache = new Dictionary<Tuple<int, string>, JobModel> ();
public static JobModel Get(int jobId, string model)
{
lock (_padlock) {
JobModel jobModel;
var key = Tuple.Create (jobId, model);
var success = _jobModelCache.TryGetValue (key, out jobModel);
Console.WriteLine (success);
if (success)
return jobModel;
else {
jobModel = new JobModel (jobId, model);
_jobModelCache [key] = jobModel;
return jobModel;
}
}
}
...
}
不幸的是,JobModel
的实例构造函数不是线程安全的。但是,我想使JobModel.Get
线程安全,并在给定相同参数的情况下返回相同的实例。
从在 C# 中实现单例模式中,我读到最好不要进行双重检查锁定。
所以我的尝试不是很好,因为它也会锁定检索。
我可以得到一些关于如何在我的用例中改进代码的建议吗?
更新
好的,我想我已经找到了解决方法,从 is-this-code-thread-safe-singleton-implementation-using-concurrent-dictionary 中的@svick
切勿创建两个具有相同参数的连接对象。如果存在,请使用它。
如果你真的需要保证这一点,那么我认为你需要 使用锁定而不是
ConcurrentDictionary
。如果可以创建重复的
Connection
(那永远不会 使用(,在极少数情况下,您可以使用重载GetOrAdd()
采用创建Connection
的 lambda :return activeConnections.GetOrAdd( Tuple.Create(param1,param2), _ => new Connection (param1, param2));
所以也许我可以做到
public class JobModel
{
static readonly Object _padlock = new object();
static readonly ConcurrentDictionary<Tuple<int, string>, JobModel> _jobModelCache = new ConcurrentDictionary<Tuple<int, string>, JobModel> ();
public static JobModel Get(int jobId, string model)
{
// Still not perfect, since it might call the instance constructor multiple times.
// Lock instance constructor as it is not thread-safe.
return _jobModelCache.GetOrAdd (Tuple.Create (jobId, model),
_ => {
lock (_padlock) {
let ret = new JobModel (jobId, model);
return ret;
}
}
);
}
}
...
}
我没有选择
ConcurrentDictionary
因为它的值可能不一致。
如果您只使用 GetOrAdd(TKey, Func<TKey, TValue>)
并且不对ConcurrentDictionary
调用任何其他方法(如 AddOrUpdate()
(,则值将是一致的:对于给定的键,GetOrAdd()
将始终返回相同的值。
不能保证的是只会为密钥创建一个值。可能发生的情况是两个线程同时调用GetOrAdd()
,这将导致创建两个值。但是其中一个将被丢弃,GetOrAdd()
将在两个线程上返回相同的值。
如果这对您来说不行,并且始终锁定在正常Dictionary
上的性能是可以接受的,请使用它。
如果这是不可接受的,那么你将需要一些更智能的解决方案。但是我认为正常的双重检查锁定在这里不起作用,因为同时读取和写入Dictionary
是不行的,而读取和写入volatile
引用是可以的,如果你做得正确的话。(编写volatile
引用,就像在正常的双重检查单例中所做的那样,是原子的。写信给Dictionary
不是。