3

假设我有充当数据缓存的单例类。多个线程将从缓存中读取,单个线程会定期刷新它。它看起来像这样:

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字段来备份我的属性?

PS,如果有人能为这个问题想出一个更具描述性的标题,我会很高兴地欢迎它。

编辑:修复了我的示例以更正明显的非原子代码。

4

4 回答 4

8

是的,在这种情况下您需要显式同步,因为另一个线程可以FooBar在您完成写入之前获取并开始读取它。

然而,如果你这样做,

public void Refresh() {
    var tmp = new Dictionary<Foo, Bar>();
    // Fill out FooBar from DB
    FooBar = tmp;
}

那么您不需要添加显式同步,因为从一个引用切换到另一个引用是原子的。

当然,这里有一个隐含的假设,即在Refresh方法之外没有写。

编辑:您还应该从自动实现的属性切换到手动实现的属性,并使用volatile修饰符声明后备变量。

于 2012-07-31T00:54:18.990 回答
2

您的示例不是线程安全的。Dictionary 不是线程安全的类,并且在执行 Refresh 时任何线程都可能正在读取。您可以使用 a lockaround 或使用其中一个线程安全类,例如ConcurrentDictionary.

于 2012-07-31T00:54:19.973 回答
1

好吧,我们同意您当前的代码不是线程安全的。所以,你必须使用同步特性,因为FooBar是你的临界区

如果你顺其自然public,你会期望班外的人DataStore会采取相应的行动。然而,这是一个糟糕的设计决策。

因此,我建议您将所有内容包装到您当前的类中,如下所示:实现线程安全字典的最佳方法是什么?

于 2012-07-31T01:03:28.793 回答
1

因为您公开地公开字典,所以您编写的代码围绕访问字典本身的方法会遇到更多问题。正如@Icarus 指出的那样,您应该使用ConcurrentDictionary,但我认为任何形式的实例锁定都对您没有帮助。

您可以轻松地将一个线程添加到集合中,而另一个线程正在对其进行迭代。

编辑我在说什么.. 永远不要公开静态字典或任何其他集合类型。始终使用并发版本

于 2012-07-31T01:04:27.633 回答