如何改进多线程中的对象字典缓存锁(即带有参数的单例)


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不是。

最新更新