views:

1262

answers:

2

Hello,

Jon Skeet's excellent article at http://www.yoda.arachsys.com/csharp/singleton.html and other articles I've read make it clear that that double-check locking doesn't work in both C# and Java unless one explicitly marks the instance as "volatile." If you don't, the check of comparing it to null could possibly return false even though the instance constructor hasn't finished running. In Mr. Skeet's third sample, he states this clearly: "The Java memory model doesn't ensure that the constructor completes before the reference to the new object is assigned to instance. The Java memory model underwent a reworking for version 1.5, but double-check locking is still broken after this without a volatile variable (as in C#)"

However, most everyone agrees (including Mr. Skeet, in samples four and five in his article), that the use of static initialization is a simple way to get a threadsafe singleton instance. He states that "static constructors in C# are specified to execute only when an instance of the class is created or a static member is referenced, and to execute only once per AppDomain."

That makes sense, but what seems to be missing is the guarantee that the reference to the new object is assigned only after the constructor completes - otherwise we'd get the same kind of issue that makes double-check locking fail unless you mark the instance as volatile. Is there a guarantee that, when using static initialization to call the instance constructor (as opposed to calling the instance constructor from a property's get{}, like we do with double-check locking), that the constructor will fully complete before any other thread can get a reference to the object?

Thanks!

+2  A: 

Yes; the guarantee is in the statement that it will only execute once per AppDomain.

It could only be unsafe if it could execute more than once; as stated, it can't, so all is well :)

Noon Silk
+3  A: 

that the constructor will fully complete before any other thread can get a reference to the object?

The static initializer will be invoked once only (by the system, at least) per AppDomain, and in a synchronized way, taking "beforefieldinit" into account. So assuming you don't do anything bizarre, any static fields assigned in the static initializer should be OK; any other attempts to use the static field should get held (blocked) behind the static constructor.

the reference to the new object is assigned only after the constructor completes

It happens when it happens. Any static field initializers happen before what you typically think of as the constructor, for example. But since other threads are blocked, this shouldn't be an issue.

However:

  • if your static initializer itself passes a reference outside (by calling a method with the reference as an argument (including "arg0"), then all bets are off
  • if you use reflection to invoke the static constructor (yes, you can do this), crazyness often follows
Marc Gravell
I think this answers my question, but a couple follow ups: - You say that other threads are blocked - this is not the case with instance constructors, correct? Otherwise the double-check locking issue would not exist? - Can you clarify your first bullet point? For example, if I have a static Dictionary<int, int> object, and in the static constructor for the class I "new" it and then call myDict.Add() a bazillion times to add a bunch of entries, is it guaranteed that all those Add() calls will complete before any other thread can use myDict?
2: yes; as long as it happens inside the static .cctor, the other threads should be blocked. wrt instance constructors... how could 2 threads talk to an instance during construction?
Marc Gravell
Bad example on my part. See the 3rd sample here: http://msdn.microsoft.com/en-us/library/ms998558.aspx. Unless you set "instance" volatile, it's possible that the compiler inlines the constructor, and the variable called "instance" can get a reference *before* the object is constructed. Another thread could get a hold of this instance, causing the first check of the double-check to return false, and then that thread can end up using an unconstructed instance. I'm trying to determine if the simple act of using type initialization completely prevents this, or something like it, from happening.