[Mono-list] Class library developers: locking issues to keep in mind

Alexander Klyubin klyubin@aqris.com
Mon, 29 Oct 2001 08:23:44 +0200

Clever, but broken :)

Step by step (once again from Java point of view):
1. Classloading ensures that static initializer is called only once and 
no thread can access the class before it it initialized. Hence, every 
thread has initializer poiting to RealInit at first.
2. One thread (A) executes RealInit, making initializer point to 
DummyInit and singleton to Singleton instance in SHARED MEMORY. Local 
copies of other threads need not yet point to DummyInit as they have not 
executed any read barrier on this initializer field. So, depending on 
JVM implementations other threads can in principle keep on having 
initializer point to RealInit and singleton to null until RealInit for 
that thread is called.

Flaw #1: Now, the order in which JVM sends updates from local memory to 
shared mememory is arbitrary. So, when thread A sends its updates of 
initializer and singleton to shared memory, they can arrive in different 
order. Let's say, first initializer, then singleton. In this case, some 
other thread might execute following code:

public static object GetHeavyObject() {
   return singleton; // local copy of singleton still null

This is because no read barrier is done in GetHeavyObject to ensure that 
both initializer and singleton have been loaded from shared memory to 
thread local memory.

Flaw #2: Assuming that initializer points to DummyInit, singleton may 
still point not to null but to a incompletely initialized instance of 
Singleton instance.

Flaw #3: A minor one. The order of sending updated to shared memory 
might be vice versa: first singleton, then initializer. In that case, 
RealInit might be executed several times for one thread, because 
initializer is not always changed to DummyInit inside RealInit. If 
singleton is not null, initializer will still keep on poiting to ReadInit.

Alexander Klyubin

Serge wrote:

>>making reference volatile does not save you from the situation
>>when the reference is OK, but the object it points to has not been
>>completely initialized
> That's right, it wont help. This is true for both UP and MP machines.
> As long as constructor inlining is a valid optimization for JIT (and I
> suppose it is).
> I guess, it is possible to fix this by disabling inlining using
> MethodImplAttribute:
>    [MethodImplAttribute(MethodImplOptions.NoInlining)]
>    private static MyObject Create () {
>         return new MyObject ();
>    }
> then
>  get {
>     if (obj != null) return obj;
>       lock (typeof (X)) {
>          if (obj == null) obj = Create ();
>          return obj;
>       }
>  }
> This won't solve SMP issues though,
> one way is to use Threading.Interlocked methods and have an additional int
> to flag object initialization. Using TLS is another way (but I don't think
> this is faster than mere synchronization).
> However, I have an idea for DCL replacement.
> Why not use function pointers (or delegates) to replace the original
> (locked) initializer after its first execution with empty non-synched
> method?
> See attached sources (OneShot.il/cs) - IL code uses raw function pointers
> and C# version uses delegates, but I think this should work for Java too
> with two object references (for example two Runnable objects).
> This pattern can be applied to both static and instance fields.
> Have fun,
> Sergey