Constructors and instruction reordering

The article you cited is conceptually correct. It’s somewhat imprecise in its terminology and usage, as is your question, and this leads to potential miscommunication and misunderstandings. It may seem like I’m harping on terminology here, but the Java Memory Model is very subtle, and if the terminology isn’t precise, then one’s understanding will suffer.

I’ll excerpt points from your question (and from comments) and provide responses to them.

The assignment of the value returned by a constructor may be reordered with respect to instructions inside the constructor.

Almost yes… it isn’t instructions but memory operations (reads and writes) that may be reordered. A thread could execute two write instructions in a particular order, but the arrival of the data in memory, and thus the visibility of those writes to other threads, may occur in a different order.

I think it’s guaranteed that from the perspective of the thread executing MyInt a = new MyInt(42), the assignment of x has a happens-before relationship with the assignment of a.

Again, almost. It is true that in program order is that the assignment to x occurs prior to the assignment to a. However, happens-before is a global property that applies to all threads, so it doesn’t make sense to talk about happens-before with respect to a particular thread.

But both of these values may be cached in registers, and they may not be flushed to main memory in the same order they were originally written. Without a memory barrier, another thread could therefore read the value of a before the value of x has been written.

Yet again, almost. Values can be cached in registers, but parts of the memory hardware such as cache memory or write buffers can also result in reorderings. Hardware can use a variety of mechanisms to change ordering, such as cache flushing or memory barriers (which generally don’t cause flushing, but merely prevent certain reorderings). The difficulty with thinking about this in terms of hardware, though, is that real systems are quite complex and have different behaviors. Most CPUs have several different flavors of memory barriers, for instance. If you want to reason about the JMM, you should think in terms of the model’s elements: memory operations and synchronizations that constrain reorderings by establishing happens-before relationships.

So, to revisit this example in terms of the JMM, we see a write to the field x and a write to a field a in program order. There is nothing in this program that constraints reorderings, i.e. no synchronization, no operations on volatiles, no writes to final fields. There is no happens-before relationship between these writes, and therefore they can be reordered.

There are a couple ways to prevent these reorderings.

One way is to make x final. This works because the JMM says that writes to final fields before the constructor returns happen-before operations that occur after the constructor returns. Since a is written after the constructor returns, the initialization of the final field x happens-before the write to a, and no reordering is allowed.

Another way is to use synchronization. Suppose the MyInt instance were used in another class like this:

class OtherObj {
    MyInt a;
    synchronized void set() {
        a = new MyInt(42);
    }
    synchronized int get() {
        return (a != null) ? a.getValue() : -1;
    }
}

The unlock at the end of the set() call occurs after the writes to the x and the a fields. If another thread calls get(), it takes a lock at the beginning of the call. This establishes a happens-before relationship between the lock’s release at the end of set() and the lock’s acquisition at the beginning of get(). This means that the writes to x and a cannot be reordered after the beginning of the get() call. Thus the reader thread will see valid values for both a and x and can never find a non-null a and an uninitialized x.

Of course if the reader thread calls get() earlier, it may see a as being null, but there is no memory model issue here.

Your Foo and Bar examples are interesting and your assessment is essentially correct. Writes to array elements that occur before assignment to a final array field cannot be reordered after. Writes to array elements that occur after the assignment to the final array field may be reordered with respect to other memory operations that occur later, so other threads may indeed see out-of-date values.

In the comments you had asked about whether this is an issue with String since it has a final field array containing its characters. Yes, it is an issue, but if you look at the String.java constructors, they are all very careful to make the assignment to the final field at the very end of the constructor. This ensures proper visibility of the contents of the array.

And yes, this is subtle. 🙂 But the problems only really occur if you try to be clever, like trying to avoid using synchronization or volatile variables. Most of the time doing this isn’t worth it. If you adhere to “safe publication” practices, including not leaking this during the constructor call, and storing references to constructed objects using synchronization (such as my OtherObj example above), things will work exactly as you expect them to.

References:

Leave a Comment