Object publication through constructor

2019-09-06 18:42发布

问题:

Consider the following class:

class Ideone
{
    private Map<String, String> m;
    public Ideone(){
        synchronized(this){
            m = new ConcurrentHashMap<>();
        }
    }

    public synchronized Map<String, String> getM(){
        return Collections.unmodifiableMap(m); //effectively immutable
    }
}

In order to allow other classes to observe Ideone's internal state, we should publish its internal state safely (with correct synchronization). If we don't do this, it's not guaranteed that another thread read the correct value (not the default one). For example:

public volatile Ideone ideone;

public void init(){
    ideone = new Ideone();
}

I think if we didn't synchronize the construction and getter, like

class Ideone
{
    private Map<String, String> m;
    public Ideone(){
            m = new ConcurrentHashMap<>();
    }

    public Map<String, String> getM(){
        return Collections.unmodifiableMap(m); //effectively immutable
    }
}

there would be no guarantees to observe the correct state value (default value, for instance).

But as said in this answer, such synchronization is not desirable as long as it allows this to escape.

QUESTION: Why does the synchronization in a constructor allow this to escape?

回答1:

The answer meant that the only reason for using synchronize(this) was for the case where the this reference had escaped from the constructor.

But that reasoning itself is incorrect. I've added another answer to that question: https://stackoverflow.com/a/34672811/981744

You have shown a case where, at first sight, your use of synchronized(this) in the constructor is reasonable, because it indeed ensures that getM() will the correct value for instance variable m when invoked from another thread.

And if you didn't have these synchronized blocks, that wouldn't have been the case necessarily - it was possible for another thread to see the value null for field m even after the constructor was complete, because there was no happens-before relationship between the assignment to m in the constructor on the one thread, and the read of m in the method getM on the other thread.

But: how did you pass the instance of Ideone from the one thread to the other thread? If you did this in a field without any synchronization mechanism, so without any happens-before relation, then the second thread is not guaranteed to see the whole instance of Ideone at all.

If is sees the instance, then the data in the instance if correct, but it may also see the value null in that case.

However if you did use a synchronization mechanism to pass the instance of Ideone, then that mechanism already created a happens-before relationship, and your use of synchronized in the constructor and in getM wasn't necessary anymore.

And since all safe mechanisms for passing objects between threads involve a synchronization mechanism that sets up a happens-before relationship, it is (almost) never necessary to do what you do with synchronized in the constructor.



回答2:

You have misunderstood the answer.

Let's understand what synchronized(this) does inside a Constructor.

For the synchronized(this) statement to be useful, two threads should be accessing a synchronized block with the same object simultaneously.

Now, one of the threads is inside the constructor, which means the object is just being created...

And for some other thread to have a reference to that object, you should have leaked the current object (this) from the constructor in some place.

Your code does NOT leak it, but again synchronized(this) doesn't have any value inside the constructor in your code.