[concurrency-interest] synchronized constructors

Zhong Yu zhong.j.yu at gmail.com
Fri Dec 16 02:58:50 EST 2011


I'm totally sympathetic to this design goal - if an object claims to
be thread safe, it should survive unsafe publication without problems.
I believe all java.util.concurrent classes have this property.

However, `synchronized` constructor cannot help to reach that goal.

    foo = new Foo();

is roughly equivalent to

    [A]    tmp = allocate memory space for sizeof(Foo)
    [B]    zero the space. (this may occur before [A])
    [1]    tmp.Foo(); // as if the constructor is an instance method
    [2]    foo = tmp;

[2] can be reordered before [1], so other threads may observe
blank/partial state through `foo` reference. This reordering is
allowed even if `Foo()` is synchronized.

This problem affects some "thread safe" classes like java.util.Vector.
If a Vector object is unsafely published, program can crash
unexpectedly. I bet that's a huge surprise to most Java programmers.

But I don't think the "unenlightened mass" are to blame. We have this
strong intuition that an object comes to existence only after
construction. Nobody outside of construction should observe a
partially constructed object (unless `this` is leaked during
construction).

If JMM has this simple "whole-birth" guarantee, many man-hours of
anguish will be saved. You can innocently do a double checked locking,
and nobody is going to yell at your naivety.

Unfortunately JMM doesn't have this guarantee. Allegedly such
guarantee carries a performance penalty. The only work around is
through `final` fields. Neither `synchronized` nor `volatile` is
useful for this purpose (many people are mistaken on this point; it is
due to their whole-birth intuition, subconsciously adding a
synchronization order from end of constructor to begin of instance
methods)

We are encouraged to use `final` fields whenever we can. One crucial
reason is for the memory effect guarantee. However, using `final`
fields will incur the same performance penalty of whole-birth
guarantee. We have a great contradiction here. Either `final` is not
cheap, so we should avoid `final` fields if we can; or `final` is
cheap, so whole-birth guarantee can be established cheaply, yielding
`final` unnecessary.

JMM does guarantee that [2] cannot be reordered before [B], otherwise
other threads can observe garbage state. Let's call this the "blank"
guarantee. It's also a mystery to me why whole-birth guarantee can be
fundamentally more expensive than blank guarantee.

Note that not everybody promotes the use of `final` though. We can
sense how reserved David Holmes is through his words: " #Sometime# a
class needs to add #some# protection against unsafe-publication that
might violate #important# semantic guarantees of the object"

(We had a similar discussion in a September thread titled "Interesting
anti-pattern with volatile"; however I cannot find it on the mailing
list archive, so no link.)

Zhong Yu

P.S. a "fix" to Vector's vulnerability to unsafe publication

class Vector

    final Params ctorParams;

    Vector(params)
        ctorParams = new Params(params);

    boolean initDone;

    void ensureInit()
        if(!initDone)
            initialize this with ctorParams ...
            initDone = true;

    synchronized
    public Foo anyOtherMethod(Bar)
        ensureInit();
        actual work...

Of course, this solution sucks. The role of constructor is severely weakened.


More information about the Concurrency-interest mailing list