[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() {
DummyInit();
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.
Regards,
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
>
>
>
>
>
>
>
>
>