跨共享实例的线程安全性(C#)

时间:2009-03-30 18:39:42

标签: c# multithreading

我使用工厂模式创建自定义对象,如果可能,从缓存加载。 自定义对象上没有静态成员或函数。

假设2个线程调用工厂并且都从缓存返回对同一对象的引用。 (即没有新的操作员,在下面的答案中回答,从集合中返回的对象)

如果我想修改类内部的私有实例成员:

a)Shoulb我先把它锁起来? b)变化是否会反映在两个线程中?

我对两个问题都假设是,但同时感觉线程有不同的类实例。

我必须在这里有一些基本的东西吗?为什么我觉得我有?

===============

按照前几个答案,我确认了我的想法,谢谢。

我想我真正想知道的是,如果对象几乎只是只读的,即在创建后它们只有一个可以更改的实例成员,在读取属性时是否需要执行任何锁定不受那个可更改的实例成员的影响?

我再次假设没有,但我必须重视集体StackOverflow大脑信任的第二意见:)

6 个答案:

答案 0 :(得分:2)

读取和写入大多数原始类型,字符串和对象引用是原子的(除了非易失性long和double之外的所有内容)。这意味着如果您只是单独读取或写入字段或其他变量,则不需要锁定。所以,如果您看到这样的代码(我保证您愿意),您可以愉快地消除不必要且昂贵的锁定:

public SomeClass SomeProperty
{
    get
    {
        lock (someLock)
        {
            return someField;
        }
    }

    set
    {
        lock (someLock)
        {
            someField = value;
        }
    }
}

但是,如果要更改值,例如将其增加1,或者如果要读取多个相关变量,则需要锁定(如果需要合理的结果)。

值得注意的是,Hashtable和Dictionary支持多个并发读取器而无需任何类型的锁定。

答案 1 :(得分:1)

您假设线程具有不同的对象实例,这是不正确的。线程指向同一个对象;因此,您必须同步访问可能由多个线程访问的任何成员,无论是通过属性访问,方法调用等等。

答案 2 :(得分:1)

它应该是同一个对象,你问题的答案是肯定的,是的。

如何检查有2个实例?

答案 3 :(得分:1)

是的,您应该在设置私有成员之前锁定。这将反映在两个线程中(因为它们是同一个对象)。

但是,您可能需要花些时间考虑如何处理锁和线程。

如果您的工厂正在创建对象并将其添加到集合中,则需要在例程周围锁定某种形式来创建对象。否则,两个线程可能会在创建之前同时请求该对象,并创建2个实例。

另外,我不建议直接设置私有成员 - 在返回的类实例中使用单个对象仅用于锁定,并使用某种形式的访问器(方法或属性),这是一个好主意。锁定该同步对象,然后设置该字段。

至于不“信任”它的两个实例 - 你总是可以做一些事情来检查调试器。添加一个检查参考相等性,甚至临时添加一个GUID到你的类在构造时设置 - 很容易验证它们是那样的。

答案 4 :(得分:0)

如果在对象上调用了两个新运算符或构造函数,那么你自己有两个同一个类的实例。

答案 5 :(得分:0)

您是否需要两个线程才能看到更改私有成员的结果?或者,如果两个线程都获得了对象,那么它是否完全没问题,但是当一个线程更改私有成员时,另一个线程看不到该更改?

这就是我要问的原因:在你的工厂里,你可以从缓存中取出对象,然后在返回之前克隆或复制它。因此,每个线程都有自己的类副本,并且该副本将具有在它们请求时在缓存对象中的状态。没有2个线程会共享完全相同的实例,因为你正在为每个线程制作一个副本。

如果符合您的要求,那么您可以避免必须锁定对象(您需要在工厂方法本身上进行同步,但在当前情况下您也需要这样做。)

但要注意“克隆”的概念。如果你有一个对象图并且你只做一个浅层克隆,那么你最终会得到仍然在线程间共享的引用,迫使你再次需要同步。如果对象是一个非常简单的对象而没有大量对其他对象的引用,那么每个线程一个副本的想法就更有​​意义了。