2010-06-23 25 views
6

我在生产中偶然发现了这个代码,我认为这可能会导致我们遇到问题。下面的代码是从单例创建对象的坚实方式吗?

internal static readonly MyObject Instance = new MyObject(); 

调用实例字段两次返回具有相同哈希码的两个对象。这些对象有可能不同吗?

我对CLI的了解说他们是相同的,因为哈希码是相同的。

请问谁能澄清?

回答

6

该字段只会初始化一次,因此您将始终获得相同的对象。这非常安全。

当然,从多个线程使用静态对象时必须小心。如果对象不是线程安全的,则应在从不同线程访问它之前将其锁定。

+1

锁定它不是对象的责任,而是调用者的责任。但是,如果你使用了延迟初始化,那将是部分的。 – ErikHeemskerk 2010-06-23 08:12:25

+1

是的,这就是我所说的:“在访问它之前应该锁定它”。 – 2010-06-23 08:14:17

+1

正如在一个现在被删除的答案中指出的那样,如果用[ThreadStaticAttribute](http://msdn.microsoft.com/en-us/library/system.threadstaticattribute.aspx)标记'Instance' * *可能包含不同的对象):“ThreadStaticAttribute标记的静态字段不在线程之间共享,每个正在执行的线程都有一个单独的字段实例,并独立地设置和获取该字段的值,如果字段在不同线程上访问,它将包含不同的价值“。 – 2010-06-23 08:17:08

1

它是静态的,它属于类,它是只读的,所以我不能在初始化后更改,所以是的,你会得到相同的对象。

-2

你认为它们是相同的,因为哈希码相同是不正确的,GetHashCode()做你的对象的字段比较。

假设您没有超载Object.Equals,你可以做一个简单的比较相等,这是默认引用的比较:

这将输出True,顺便说一下,因为你单身的实现正确的。 A static readonly字段保证只分配一次。但是,从语义上来说,只用get-accessor实现一个属性并使用私有静态字段作为后备存储会更加正确。

+0

'GetHashCode()'不一定会在对象的字段上进行比较。 – 2010-06-23 08:11:47

+1

该领域的方法适用于'结构',单身毫无意义。对于类,它是默认散列的对象引用。更好的检查将是'ReferenceEquals(a,b)' – 2010-06-23 08:14:55

+0

@Marc Gravell:这将是一个更傻的检查,但就像我说的,*假设你没有重载Object.Equals *,'=='会照着做。 – ErikHeemskerk 2010-06-23 08:18:57

3

其他答案已评论岩石安全。这里有更多关于你的一些参考散列代码:

哈希码是相同意味着两个对象可能被认为“平等” - 不同的理念,以“相同的”。所有的哈希码真的告诉你,如果两个对象有不同的哈希码,他们是肯定不是“等于” - 因此暗示肯定不是“相同”。平等是通过覆盖.Equals()方法来定义的,并且强加的合同是如果通过该方法认为两个对象相等,则它们必须从它们的方法返回相同的值。如果两个变量的引用相同,则两个变量“相同” - 即它们指向内存中的同一个对象。

6

是的,它是安全的 - 最简单的安全单例实现。

作为比较所述哈希码来推断“它们是相同的对象”的另一点;因为我们正在谈论的引用类型在这里(单是无意义的值类型),检查的最佳方式,如果两个引用指向同一个对象是:

bool isSame = ReferenceEqual(first, second); 

这是不依赖于GetHashCode()/Equals/==实现(它看参考本身)。

5

它是由CLR提供了保障,这将正常工作,即使在类由多个线程。

类似的还有当类型初始化发生在多线程系统,但更复杂的,存在的问题:这是在335了Ecma,分区II,节10.5.3.3指定。例如,在这些情况下,两个单独的线程可能开始尝试访问单独的类型(A和B)的静态变量,然后每个线程都必须等待另一个完成初始化。

的算法的粗略轮廓,以确保点1和2以上是如下:
1.类加载时(在初始化因此先前时间)存储零或空入类型的所有静态字段。
2.如果类型已初始化,则完成。
2.1。如果类型尚未初始化,请尝试进行初始化锁定。
2.2。如果成功,记录此线程负责初始化类型并继续执行步骤2.3。
2.2.1。如果不成功,看看这个线程或任何等待这个线程完成的线程是否已经拥有了 这个锁。
2.2.2。如果是这样,返回,因为阻塞会造成死锁。此线程现在将看到该类型的未完成初始化的 状态,但不会出现死锁。
2.2.3如果不是,则阻塞,直到类型被初始化然后返回。
2.3初始化基类类型,然后初始化由此类型实现的所有接口。
2.4执行此类型的类型初始化代码。
2.5将类型标记为已初始化,释放初始化锁,唤醒等待此类型的任何线程为 初始化并返回。

需要明确的是,这是他们提出了一个CLR实现的算法,而不是你的代码。

0

它会工作得很好,在这种情况下,但如果你有纪念实例[ThreadStatic]属性然后直接初始化将无法正常工作,你将不得不使用别的东西,想偷懒的初始化,在这种情况下,你不如果使用单例的操作是“线程安全”的,那么每个线程都是单例。

问候......

0

您可能会感兴趣的事实,即初始化 的懒惰可能会有所不同。

乔恩斯基特建议,如果你愿意添加一个空的静态构造函数有关 实例时实际初始化。 为了避免以错误的方式曝光,我为您提供了关于他在单例模式中的 文章的链接。

你的问题涉及到他的文章中讨论的第四种(和建议的)单例模式实现。

辛格尔顿:singleton implementation

里面的文章,你发现在初始化的beforefieldinit和懒惰的链接的讨论。

相关问题