假设我有一个充当数据缓存的单例类。多个线程将从缓存中读取,并且单个线程将定期刷新它。它看起来像这样:
public sealed class DataStore
{
public static DataStore Instance { get { return _instance; } }
public Dictionary<Foo, Bar> FooBar { get; private set; }
static DataStore() { }
private DataStore() { }
public void Refresh() {
FooBar = GetFooBarFromDB();
}
private static readonly DataStore _instance = new DataStore();
}
我的问题基本上是Refresh()
安全,而其他线程可能正在访问FooBar
吗?我需要使用锁,还是我的获取和设置操作是原子的?我是否需要明确声明volatile
字段来备份我的属性?
P.S。,如果有人能想到这个问题的更具描述性的标题,我很乐意欢迎它。
编辑:修复了我的示例以明显纠正非原子代码。
答案 0 :(得分:8)
是的,在这种情况下,您需要显式同步,因为另一个线程可以获得FooBar
并在您完成写入之前开始阅读它。
但是,如果你这样做,
public void Refresh() {
var tmp = new Dictionary<Foo, Bar>();
// Fill out FooBar from DB
FooBar = tmp;
}
那么你就不需要添加显式同步,因为从一个引用到另一个引用的切换是原子的。
当然,这里隐含的假设是Refresh
方法之外没有写作。
编辑:您还应该从自动实现的属性切换到手动实现的属性,并使用volatile
修饰符声明支持变量。
答案 1 :(得分:2)
您的示例不是线程安全的。 Dictionary不是一个线程安全的类,任何线程都可以在执行Refresh时读取。您可以放置lock
或使用其中一个线程安全的类,如ConcurrentDictionary
。
答案 2 :(得分:1)
好吧,我们同意您当前的代码不是线程安全的。
因此,您必须使用同步功能,因为FooBar
是您的关键部分。
如果你是public
,那么期待 DataStore
课程以外的人会相应地采取行动。然而,这是一个糟糕的设计决定。
所以,我建议你把所有东西都包装到你当前的课程中,如下所示:What's the best way of implementing a thread-safe Dictionary?
答案 3 :(得分:1)
因为您公开公开字典,所以您遇到的更多问题与您编写的代码有关访问字典本身上的方法。正如@Icarus指出你应该使用ConcurrentDictionary
,但我认为任何形式的实例锁定都无法帮助你。
您可以轻松地将一个线程添加到集合中,而另一个线程则迭代它。
EDIT 我在说什么..从不暴露静态字典或任何其他集合类型。始终使用并发版本