[concurrency-interest] synchronized constructors

Roland Kuhn rk at rkuhn.info
Fri Dec 16 05:18:12 EST 2011


I don’t see anything in chapter 8 which allows synchronized to be elided at all. Viewed from a single thread, the main guarantees of synchronized are “fresh reads after lock” and “writes flushed before unlock”, and these cannot be elided. If the VM sees that nobody else is ever locking on the same object, I would assume that the “mutual exclusion” part may be elided, but not the memory effects.

Would someone more knowledgeable with VM implementations please comment?

On Dec 16, 2011, at 10:59 , Kearney, Joe wrote:

> How much of that locking is allowed to be elided away?
> 
> If you call synchronized(this) {} in a constructor, I would argue that provably no other thread can acquire the lock yet. So can the locking be compiled away, even though the induced fence would need to stay?
> 
> -----Original Message-----
> From: concurrency-interest-bounces at cs.oswego.edu [mailto:concurrency-interest-bounces at cs.oswego.edu] On Behalf Of Roland Kuhn
> Sent: 16 December 2011 09:49
> To: Zhong Yu
> Cc: concurrency-interest
> Subject: Re: [concurrency-interest] synchronized constructors
> 
> As I was recently made aware: the rules for write reordering are not as weak as you think.
> 
> http://java.sun.com/docs/books/jvms/second_edition/html/Threads.doc.html#24432
> 
> Section 8.8 of the JVM spec requires that a write cannot be moved earlier across a "lock" operation. This means that a synchronized {} in the constructor solves the issue.
> 
> Regards,
> 
> Roland
> 
> On Dec 16, 2011, at 08:58 , Zhong Yu wrote:
> 
>> 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.
>> _______________________________________________
>> Concurrency-interest mailing list
>> Concurrency-interest at cs.oswego.edu
>> http://cs.oswego.edu/mailman/listinfo/concurrency-interest
> 
> --
> [scala-debate on 2009/10/2]
> Viktor Klang: When will the days of numerical overflow be gone?
> Ricky Clarkson: One second after 03:14:07 UTC on Tuesday, 19 January 2038
> 
> 
> _______________________________________________
> Concurrency-interest mailing list
> Concurrency-interest at cs.oswego.edu
> http://cs.oswego.edu/mailman/listinfo/concurrency-interest

--
Simplicity and elegance are unpopular because they require hard work and discipline to achieve and education to be appreciated.
  -- Dijkstra




More information about the Concurrency-interest mailing list