Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Constructors and instruction reordering

I just ran across an article that makes a claim I have never heard before and cannot find anywhere else. The claim is that from the perspective of another thread, the assignment of the value returned by a constructor may be reordered with respect to instructions inside the constructor. In other words, the claim is that in the code below, another thread could read a non-null value of a in which the value of x has not been set.

class MyInt {
   private int x;

   public MyInt(int value) {
      x = value;
   }

   public int getValue() {
      return x;
   }
}

MyInt a = new MyInt(42);

Is this true?

Edit:

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. 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. Correct?

So based on axtavt's answer and the comments that follow, are these assessments of thread safety correct?

// thread-safe
class Foo() {
   final int[] x;

   public Foo() {
      int[] tmp = new int[1];
      tmp[0] = 42;
      x = tmp; // memory barrier here
   }
}

// not thread-safe
class Bar() {
   final int[] x = new int[1]; // memory barrier here

   public Bar() {
      x[0] = 42; // assignment may not be seen by other threads
   }
}

If that's correct... wow, that's really subtle.

like image 547
Kevin Krumwiede Avatar asked Jul 16 '14 19:07

Kevin Krumwiede


People also ask

What is reordering in Java?

Instruction reordering is allowed for the Java VM and the CPU as long as the semantics of the program do not change. The end result has to be the same as if the instructions were executed in the exact order they are listed in the source code.

Does order matter in constructors?

Yes it matters. In the example above, the first argument has to be an integer, and the second has to be a string. Constructors are chosen according to the rules in the language spec. TL;DR: yep, there have to be same number, and they've got to be in the same order.


2 Answers

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:

  • Goetz, Java Concurrency In Practice, Chapter 3, Sharing Objects. This includes discussion of memory visbility and safe publication.
  • Manson/Goetz, Java Memory Model FAQ. http://www.cs.umd.edu/~pugh/java/memoryModel/jsr-133-faq.html . Somewhat old, but has some good examples.
  • Shipilev, Java Memory Model Pragmatics. http://shipilev.net/blog/2014/jmm-pragmatics/ . A slide presentation and transcript of a talk given by one of Oracle's performance gurus. More than you ever wanted to know about the JMM, with some pointers to potential revisions of the JMM in future versions of Java.
  • OpenJDK 8 String.java source code. http://hg.openjdk.java.net/jdk8/jdk8/jdk/file/tip/src/share/classes/java/lang/String.java
like image 107
Stuart Marks Avatar answered Oct 21 '22 12:10

Stuart Marks


In sense of the Java Memory Model - yes. In doesn't mean that you will observe it in practice, though.

Look at it from the following angle: optimizations that may result in visible reordering may happen not only in the compiler, but also in the CPU. But the CPU doesn't know anything about objects and their constructors, for the processor it's just a pair of assignments that can be reordered if the CPU's memory model allows it.

Of course, compiler and JVM may instruct the CPU not to reorder these assignments by placing memory barriers in the generated code, but doing so for all objects will ruin performance of the CPUs that may heavily rely on such an aggressive optimizations. That's why Java Memory Model doesn't provide any special guarantees for this case.

This leads, for example, to well-known flaw in Double checked locking singleton implementation under the Java Memory Model.

like image 24
axtavt Avatar answered Oct 21 '22 12:10

axtavt