JavaMemoryModel: Race-proof mutable objects and synchronized constructors

From: Bill Pugh (pugh@cs.umd.edu)
Date: Fri Dec 01 2000 - 21:18:26 EST


Say you want to design a class Foo that is designed to work
correctly, even if references to Foo objects are passed between
threads via a data race (perhaps maliciously so). Strings are one
example of a class we need to have be race-proof.

For immutable classes, we have a solution: use final fields. But what
about for mutable classes?

The standard approach in such cases would be to simply synchronize
all of the methods of the class, but to not synchronize the
constructor. Syntactically, it is illegal to declare a constructor as
synchronized. You could fake it using a synchronized block inside the
constructor, but I've never seen code that does so.

Unfortunately, to really make the class race-proof, you need to have
the constructor be synchronized. Without a synchronized constructor,
there is nothing that forces the processor that initializes the
object to send the initializing writes from its cache out to main
memory.

The JLS states (Section 8.8.3):

"Unlike methods, a constructor cannot be abstract, static, final,
native, or synchronized.... There is no practical need for a
constructor to be synchronized, because it would lock the object
under construction, which is normally not made available to other
threads until all constructors for the object have completed their
work. "

Unfortunately, this is wrong (and has led to some confusion). While
there is no practical need in constructors for the mutual exclusion
semantics of synchronization, synchronization also has important
visibility semantics that are relevant for constructors.

I see two possible solutions:

* Say that in order to bullet-proof the class, you need to use synchronization
   in constructors. You could do this through a synchronized block, but we could
   also change the language to allow synchronized constructors.

   -- or --

* Give constructors special visibility semantics: when a thread synchronizes
   on an object, it sees all writes that were visible to the constructing thread
   at the time the object's constructor terminated. In other words, all
   constructors have the visibility semantics of being synchronized, without the
   mutual exclusion semantics of being synchronized. Assuming that you
don't make
   objects visible to other threads until after they are constructed, this is
   essentially the same as synchronizing all constructors.

Given these choices, I recommend the second option. Although either
would allow for the creation of race-proof classes, the first option
would mean that most/all existing synchronized classed would be
broken, and I think it would be very hard to get most programmers to
understand why and when constructors need to be synchronized. I don't
think choosing the second option would require more memory barriers;
for a number of reasons, we probably already need a memory barrier at
the end of each constructor on processors with weak memory models.

Thoughts?

        Bill
-------------------------------
JavaMemoryModel mailing list - http://www.cs.umd.edu/~pugh/java/memoryModel



This archive was generated by hypermail 2b29 : Thu Oct 13 2005 - 07:00:29 EDT