views:

384

answers:

5

I've heard about this happening in non thread-safe code due to improperly constructed objects but I really don't have the concept down, even after reading about in in Goetz's book. I'd like to solidify my understanding of this code smell as I maybe doing it and not even realize it. Please provide code in your explanation to make it stick, thanks.

+9  A: 

Example : in a constructor, you create an event listener inner class (it has an implicit reference to the current object), and register it to a list of listener.
=> So your object can be used by another thread, even though it did not finish executing its constructor.

     public class A {

      private boolean isIt;
      private String yesItIs;

      public A() {
        EventListener el = new EventListener() { ....};
        StaticListeners.register(el);
        isIt = true;
        yesItIs = "yesItIs";
      }
     }


An additional problem that could happen later : the object A could be fully created, made available to all threads, use by another thread ... except that that thread could see the A instance as created, yesItIs with it "yesItIs" value, but not isIt! Believe it or not, this could happen ! What happen is:

=> synchronization is only half about blocking thread, the other half is about inter-thread visibility.

The reason for that Java choice is performance : inter-thread visibility would kill performance if all data would be shared with all threads, so only synchronized data is guaranteed to be shared...

KLE
If you're mutating a static, then you already have problems. A more realistic case is where you add a reference (for instance, via a listener) to an argument passed to the constructor.
Tom Hawtin - tackline
@Tom I agree, instead of registering to StaticListeners, I could register to the same object passed through the constructor. This is often thought of as preferable, but we can see it's far from perfect :-) About it being 'realistic', your mileage may vary, but I guess that for many projects using globals do happen (whether static, or ThreadLocal, or accessed via another global) ... I found it shorter to build the short example ;-)
KLE
+5  A: 

Really simple example:

public class Test
{
    private static Test lastCreatedInstance;

    public Test()
    {
        lastCreatedInstance = this;
    }
}
Jon Skeet
This doesn't look like something that would normally happen though, even I would not think of writing this. But the escape-ing part is clear to me though. What would be the possible consequence of this code?
non sequitor
@non sequitor: Imagine if there's a lot of other initialization in the constructor... but another thread uses `lastCreatedInstance` before the constructor has finished. It could be using a half-initialized object.
Jon Skeet
A: 

This is the reason why double-checked locking doesn't work. The naive code

if(obj == null)
{
  synchronized(something)
  {
     if (obj == null) obj = BuildObject(...);
  }
} 
// do something with obj

is not safe because the assignment to the local variable can occur before the rest of the construction (constructor or factory method). Thus thread 1 can be in the BuildObject step, when thread 2 enters the same block, detects a non-null obj, and then proceeds to operate on an incomplete object (thread 1 having been scheduled out in mid-call).

Steve Gilham
Would you mind explaining the down-vote. Double-checked locking is the classic example of the "this" reference escaping.
Steve Gilham
Only that this example isn’t because `obj` is never assigned anything until `BuildObject` has completed its constructor.
Bombe
This is where you are wrong -- the assignment can happen before construction completes. See http://www.ibm.com/developerworks/java/library/j-dcl.html for a full discussion.
Steve Gilham
Steve, please read up on why double-checked locking doesn't work. It has nothing to do with references to partially-constructed objects leaking to other threads. http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html
Grandpa
Your link includes -- "Thus, a thread which invokes getHelper() could see a non-null reference to a helper object, but see the default values for fields of the helper object, rather than the values set in the constructor." That's a partially constructed object.
Steve Gilham
+2  A: 
public class MyClass{
    String name;    

    public MyClass(String s)
    {
        if(s==null)
        {
            throw new IllegalArgumentException();
        }
        OtherClass.method(this);
        name= s;
    }

    public getName(){ return name; }
}

In the above code, OtherClass.method() is passed an instance of MyClass which is at that point incompletely constructed, i.e. not yet fulfilling the contract that the name property is non-null.

Michael Borgwardt
A: 

Steve Gilham is correct in his assesment of why double checked locking is broken. If thread A enters that method and obj is null, that thread will begin to create an instance of the object and assign it obj. Thread B can possibly enter while thread A is still instantiating that object (but not completing) and will then view the object as not null but that object's field may not have been initialized. A partially constructed object.

However, the same type of problem can arrise if you allow the keyword this to escape the constructor. Say your constructor creates an instance of an object which forks a thread, and that object accepts your type of object. Now your object may have not be fully initialized, that is some of your fields may be null. A reference to your object by the one you have created in your constructor can now reference you as a non null object but get null field values.

A bit more explanation:

Your constructor can initialize every field in your class, but if you allow 'this' to escape before any of the other objects are created, they can be null (or default primative) when viewed by other threads if 1. They are not declared final or 2. They are not declared volatile

John V.
Double-checked locking isn't broken anymore. If the variable is volatile, *and the code is structured correctly,* it works just fine under the Java 5+ memory model. After lazily initialization, the "cost" is just a volatile read.
erickson
Yes adding volatile to the variable will allow it to be correctly initialized, but so would declaring it final. But even Goetz notes that despite declaring it volatile it isnt 100% full proof. What if, for example, the volatile object has fields that are declared in its constructor. You would have a volatile Object myObject, which is initialized within the constructor. And another thread invokes myObject.getSomeField().getSomeOtherField(). If someOtherField is declared in someField's constructor, it could possibly be null whereas someOtherField can be partially constructed
John V.
Creating a thread in a constructor is a good example.
Tom Hawtin - tackline