Out-of-order writes for Double-checked locking
Asked Answered
G

4

4

In the examples mentioned for Out-of-order writes for double-checked locking scenarios (ref: IBM article & Wikipedia Article)

I could not understand the simple reason of why Thread1 would come of out synchronized block before the constructor is fully initialized. As per my understanding, creating "new" and the calling constructor should execute in-sequence and the synchronized lock should not be release till all the work in not completed.

Please let me know what I am missing here.

Gielgud answered 25/6, 2012 at 18:47 Comment(1)
Your question is not clear enough for me to see exactly what you are missing. Why not post a concrete code example and indicate where your confusion lies?Amputate
T
12

The constructor can have completed - but that doesn't mean that all the writes involved within that constructor have been made visible to other threads. The nasty situation is when the reference becomes visible to other threads (so they start using it) before the contents of the object become visible.

You might find Bill Pugh's article on it helps shed a little light, too.

Personally I just avoid double-checked locking like the plague, rather than trying to make it all work.

Torchier answered 25/6, 2012 at 18:54 Comment(5)
+1 I agree, avoid double locking. Like I often say, keep it simple.Waldos
@Jon I went through the article that you had posted as link. I just wonder what would happen if the assignment to the reference is performed before constructor is calle , but constructor fails. Will it continue referencing the memory space.Feudalize
@VishalK: Um, not sure to be honest.Torchier
@JonSkeet Is the out-of-order write problem still topical with Java 6+?Heddle
@sp00m: I believe so, yes.Torchier
D
1

Thread 2 checks to see if the instance is null when Thread 1 is at //3 .

public static Singleton getInstance()
{
if (instance == null)
{
synchronized(Singleton.class) {  //1
  if (instance == null)          //2
    instance = new Singleton();  //3
  }
}
 return instance;//4
}

At this point the memory for instance has been allocated from the heap and the pointer to it is stored in the instance reference, so the "if statement" executed by Thread 2 returns "false". Note that because instance is not null when Thread2 checks it, thread 2 does not enter the synchronized block and instead returns a reference to a " fully constructed, but partially initialized, Singleton object."

Diagonal answered 25/6, 2012 at 19:18 Comment(8)
Memory having been allocated has nothing to do with the instance reference being written. The write to the instance has been completed only after //3.Amputate
That is my question: since Thread1 is still processing the constructor (because of line3 "new Singleton()") and this call is within the synchronized block, how does Thread2 get a chance to get into the synchronized block and check for null?Gielgud
Thread2 does not enter the synchronized block in this case. It checks that the instance is not null , and since it's not , it skips over the the synchronized block to line 4 , and returns the instanceDiagonal
your statement: "Thread 2 checks to see if the instance is null when Thread 1 is at //3" seems incorrect (with my current/incorrect knowledge). Thread 2 whould check the null condition once it gets into synchronized block, which will be done only after line3 in completely executed by Thread1. Please correct me.Gielgud
Please note that the critical section begins with the keyword synchronizedDiagonal
Hi Vadim, I noted it, but it did not help me. Can you please explain in little more detail? Why Thread1 leaves before fishing everything to let Thread2 enters the synchronized block?Gielgud
Thread1 executes the first "if statement" - it evaluates to "true"- this is the point when Thread1 enters the critical section and acquires the lock.A couple of things happen at //3 : 1) the memory for the singleton object is being allocated; 2) a pointer to the allocated memory is assigned to instance ;3) instructions inside the constructor are being executed. When Thread1 releases the lock, it flushes from cache to the main memory. The problem at hand is when Thread2 executes the first "if statement" after Thread1 has assigned the pointer but before the flush.Diagonal
I got it now. But this theory is little strange to me. Why the flushing issue will not happen in the following code? someObject.setSomeVlaue(10); if(someObject.getSomeValue != 10) { doSomething(); }Gielgud
A
1

The code in question is here:

public static Singleton getInstance()
{
  if (instance == null)
  {
    synchronized(Singleton.class) {  //1
      if (instance == null)          //2
        instance = new Singleton();  //3
    }
  }
  return instance;
}

Now the problem with this cannot be understood as long as you keep thinking that the code executes in the order it is written. Even if it does, there is the issue of cache synchronization across multiple processors (or cores) in a Symmetrical Multiprocessing architecture, which is the mainstream today.

Thread1 could for example publish the instance reference to the main memory, but fail to publish any other data inside the Singleton object that was created. Thread2 will observe the object in an inconsistent state.

As long as Thread2 doesn't enter the synchronized block, the cache synchronization doesn't have to happen, so Thread2 can go on indefinitely without ever observing the Singleton in a consistent state.

Amputate answered 25/6, 2012 at 19:31 Comment(4)
Thanks for explanation. But my question is simple: Thread1 acquired lock. It is executing line3 (creating object). I understand from your explanation that line 3 is composed of multiple lines. But my question is, no matter how many steps to execute line3, Thread1 should not leave the synchronized block (for thread2) till all the steps of line3 are completed for Thread1(including the constructor call).Gielgud
My answer already covers exactly what you ask, you just don't fully understand it yet. Thread1 can have completed the whole getInstance method and the result may be that Thread2 now observes the non-null instance reference, but not the initialized contents of the Singleton object. The reason is given in the answer.Amputate
Appologies, but it seems to be little new,different and difficult concept. Does that mean this: line3 has many steps, including calling constructor. This step (of calling constructor) is though called but writing to the object happens later, after thread1 started using it? If thats correct, does that mean that writing to the values of constructed happens asynchronously but some another thread (and not the thread1)?Gielgud
You need to stop thinking in terms of Java and start thinking in terms of the native code that implements this. On a multiprocessor machine each processor has its own memory cache which may or may not be synchronized with the main memory in the RAM. If thread1 executes on one processor and Thread2 on another, Thread2 can have a cached copy of locations belonging to the Singleton object, but with contents of before those locations were allocated to the object.Amputate
P
0

There's a general problem with code not being executed in the order it's written. In Java, a thread is only obligated to be consistent with itself. An instance created on one line with new has to be ready to go on the next. There's no such oblgation to other threads. For instance, if fieldA is 1 and 'fieldB' is 2 going into this code on thread 1:

fieldA = 5;
fieldB = 10;

and thread 2 runs this code:

int x = fieldA;
int y = FieldB;

x y values of 1 2, 5 2, and 5 10 are all to be expected, but 1 10--fieldB was set and/or picked up before fieldA--is perfectly legal, and likely, as well. So double-checked locking is a special case of a more general problem, and if you work with multiple threads you need to be aware of it, particularly if they all access the same fields.

One simple solution from Java 1.5 that should be mentioned: fields marked volatile are guaranteed to be read from main memory immediately before being referenced and written immediately after. If fieldA and fieldB above were declared volatile, an x y value of 1 10 would not be possible. If instance is volatile, double-checked locking works. There's a cost to using volatile fields, but it's less than synchronizing, so the double-checked locking becomes a pretty good idea. It's an even better idea because it avoids having a bunch of threads waiting to synch while CPU cores are sitting idle.

But you do want to understand this (if you can't be talked out of multithreading). On the one hand you need to avoid timing problems and on the other avoid bringing your program to a halt with all the threads waiting to get into synch blocks. And it's very difficult to understand.

Project answered 26/6, 2012 at 19:45 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.