Reputation: 27435
I'm reading B. Goetz Java Concurrency In practice and now I'm at the section 3.5
about safe publication. He stated:
// Unsafe publication
public Holder holder;
public void initialize() {
holder = new Holder(42);
}
This improper publication could allow another thread to observe a partially constructed object.
I don't see why it is possible to observe a partially constructed subobject. Assume, that the constructor Holder(int)
does not allow this
to escape. So, the constructed reference can be observed only by the caller. Now, as JLS 17.7 stated:
Writes to and reads of references are always atomic, regardless of whether they are implemented as 32-bit or 64-bit values.
it is impossible for thread to observe a partially constructed object.
Where was I wrong?
Upvotes: 12
Views: 698
Reputation: 27190
Now, as JLS 17.7 stated, "Writes to and reads of references are always atomic..."
That might mean less than what you think it means. All it means is, that the value assigned to a reference variable will never be torn.
If some thread updates a non-volatile reference variable that initially referred to object A, changing it to refer to object B instead, then other threads will either see the A reference or the B reference when they examine the variable. No thread will ever see an invalid reference composed of some bits from the old value and some bits from the new value.
In particular, the requirement that all reference reads and writes be "atomic" does not mean that all reference reads and writes have volatile
semantics. They don't; If one thread updates a non-volatile reference variable to point to a newly constructed object, then another thread could get the new reference when it examines the variable, but see the object itself in a partially initialized or uninitialized state.
Upvotes: 1
Reputation: 43401
So, the constructed reference can be observed only by the caller.
That's where your logic breaks, though it seems like a perfectly reasonable thing to say.
First things first: The atomicity that 17.7 mentions only says that when you read a reference, you'll either see all of a previous value (starting with its default value of null
) or all of some subsequent value. You'll never get a reference with some bits corresponding to value 1 and some bits corresponding to value 2, which would essentially make it a reference into a random place in the JVM heap — which would be terrible! Basically they're saying, "the reference itself will either be null, or point to a valid place in memory." But what's in that memory, that's where things can get weird.
I'll assume this simple Holder:
public class Holder {
int value; // NOT final!
public Holder(int value) { this.value = value; }
}
Given that, what happens when you do holder = new Holder(42)
?
value = 0
)<new instance>.value
to the incoming value (42).Holder.holder
to this new referenceThe problem is that another thread can view these events in any order, since there are no synchronization points between them. That's because constructors don't have any special synchronization or happens-before semantics (that's slight lie, but more on that later). You can see the full list of "synchronized-with" actions at JLS 17.4.4; note that there's nothing there about constructors.
So, another thread might see those actions ordered as (1, 3, 2). This means that if some other event is ordered between events 1 and 3 — for instance, if someone reads Holder.holder.value
into a local var — then they'll see that newly allocated object, but with its values before the constructor has run: you'd see Holder.holder.value == 0
. This is called a partially constructed object, and it can be pretty confusing.
If the constructor had multiple steps (setting multiple fields, or setting and then changing a field), then you can see any ordering of those steps. Pretty much all bets are off. Yikes!
final
fieldsI mentioned above that I lied when I asserted that constructors don't have any special synchronization semantics. Assuming you don't leak this
, there's one exception to that: any final
fields are guaranteed to be seen as they were at the end of the constructor (see JLS 17.5).
You can think of it as there being a kind of synchronization point between steps 2 and 3, but it only applies to final
fields.
final
fields. So, if you have a final List<String>
, and your constructor initializes it and then adds some values, then all threads are guaranteed to see that list with at least the state that it had at the end of the constructor, including those add
calls. (If you modify the list after the constructor, without synchronization, then all bets are off again.)That's why it was important in my example above that value
was not final. If it had been, then you wouldn't be able to see Holder.holder.value == 0
.
Upvotes: 19
Reputation: 1654
The construction of Holder
consists roughly of three parts:
holder
However, for performance reasons, these are reordered and will probably run as follows:
holder
So it is possible for a partially constructed Object already to be assigned to a field. From a single threaded perspective this poses no problem whatsoever. But from a multithreaded perspective this leads to obvious problems.
Upvotes: 3