12

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?

St.Antario
  • 26,175
  • 41
  • 130
  • 318
  • 1
    btw, I just clicked through to the link you provided with JCIP, and I noticed it's the full text of the book! Afaik, JCIP is still under copyright, and that copy shouldn't be up there. That's something for the github people to monitor and deal with, but imo it's improper to share the link here. – yshavit Dec 30 '15 at 08:22
  • @yshavit Yes, but I thought since it was published on github, it might as well be published here... I presum Brian Goetz knows about it... – St.Antario Dec 30 '15 at 08:33
  • 'It's on github' is not a guarantee of legal distribution any more than 'I can torrent it'. There is no freely licensed copy of the book on its site http://jcip.net. This is almost certainly a link to a pirated copy and you should remove it. – pvg Dec 30 '15 at 09:40
  • Possible duplicate of [Memory barriers and coding style over a Java VM](http://stackoverflow.com/questions/3964317/memory-barriers-and-coding-style-over-a-java-vm) – Erick G. Hagstrom Dec 30 '15 at 13:42
  • [without final this is proveable](https://stackoverflow.com/questions/5066866/testing-initialization-safety-of-final-fields/59385508#59385508) – Eugene Jan 01 '20 at 17:00

3 Answers3

19

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.

Setting up a simple example

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)?

  1. the JVM allocates some space for the new Holder object, with default values for all its fields (ie, value = 0)
  2. the JVM invokes the Holder constructor
    • the JVM sets <new instance>.value to the incoming value (42).
    • the constructor completes
  3. the JVM returns the reference to that object we just allocated, and sets Holder.holder to this new reference

Reordering makes life hard (but it also makes programs fast!)

The 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!

Constructors and final fields

I 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.

  • It doesn't apply to non-final fields
  • It doesn't apply transitively to other synchronization points.
  • It does, however, extend to any state that you access through the 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.

yshavit
  • 42,327
  • 7
  • 87
  • 124
  • 1
    `Reordering makes life hard (but it also makes programs fast!)` Percisely, I forgot about this optimization. So, it's possible for JVM to reorder the statements in a way the object will be published with just deafult vaues... – St.Antario Dec 30 '15 at 08:15
  • `If you modify the list after the constructor, without synchronization, then all bets are off again.` I have a question about this. I thought it depended on the list implementation. For instance, if the internal state of the list declared as `volatile`, we observe all state changes, right? – St.Antario Dec 31 '15 at 06:03
  • Yes, that's true. If the list itself is thread safe, you're fine. I should have been clearer about that. :) I meant synchronization in the happens-before sense, which would include writes/reads to volatile fields. – yshavit Dec 31 '15 at 06:43
3

The construction of Holder consists roughly of three parts:

  1. allocate memory
  2. run initialisers
  3. assign value field holder

However, for performance reasons, these are reordered and will probably run as follows:

  1. allocate memory
  2. assign value field holder
  3. run initialisers

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.

M Platvoet
  • 1,679
  • 1
  • 10
  • 14
1

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.

Solomon Slow
  • 25,130
  • 5
  • 37
  • 57