【发布时间】:2023-03-06 01:33:01
【问题描述】:
我正在使用 3.5 .NET Framework 进行开发,我需要在多线程场景中使用缓存,并为其项目使用延迟加载模式。 在阅读了网络上的几篇文章后,我尝试编写自己的实现。
public class CacheItem
{
public void ExpensiveLoad()
{
// some expensive code
}
}
public class Cache
{
static object SynchObj = new object();
static Dictionary<string, CacheItem> Cache = new Dictionary<string, CacheItem>();
static volatile List<string> CacheKeys = new List<string>();
public CacheItem Get(string key)
{
List<string> keys = CacheKeys;
if (!keys.Contains(key))
{
lock (SynchObj)
{
keys = CacheKeys;
if (!keys.Contains(key))
{
CacheItem item = new CacheItem();
item.ExpensiveLoad();
Cache.Add(key, item);
List<string> newKeys = new List<string>(CacheKeys);
newKeys.Add(key);
CacheKeys = newKeys;
}
}
}
return Cache[key];
}
}
如您所见,Cache 对象既使用存储真实键值对的字典,也使用仅复制键的列表。 当线程调用 Get 方法时,它会读取静态共享密钥列表(声明为 volatile)并调用 Contains 方法以查看密钥是否已经存在,如果不存在,则在开始延迟加载之前使用双重检查锁定模式。在加载结束时,会创建一个新的键列表实例并将其存储在静态变量中。
显然,我处于重新创建整个键列表的成本与单个项目加载的成本几乎无关的情况。
我希望有人能告诉我它是否真的是线程安全的。 当我说“线程安全”时,我的意思是每个读取器线程都可以避免损坏或脏读取,并且每个写入器线程只加载一次丢失的项目。
【问题讨论】:
-
你看过 ReaderWriterLock 吗? msdn.microsoft.com/en-us/library/…
-
@Kip9000:该类实际上已被弃用,取而代之的是
ReaderWriterLockSlim。 -
@Jon 我错了,我的意思是写 ReaderWriterLockSlim。根据 MSDN,ReaderWriterLock 似乎并没有被弃用,而是首选并且更好。
-
你用两次 O(n) 查找来换取一个锁。这只能为琐碎的列表带来回报,不超过少数缓存项目。至少使用 HashSet。然后计时。
-
为什么不使用
Dictionary<TKey, TValue>.ContainsKey而不是单独跟踪密钥?