9

The Java memory model guarantees a happens-before relationship between an object's construction and finalizer:

There is a happens-before edge from the end of a constructor of an object to the start of a finalizer (§12.6) for that object.

As well as the constructor and the initialization of final fields:

An object is considered to be completely initialized when its constructor finishes. A thread that can only see a reference to an object after that object has been completely initialized is guaranteed to see the correctly initialized values for that object's final fields.

There's also a guarantee about volatile fields since, there's a happens-before relations with regard to all access to such fields:

A write to a volatile field (§8.3.1.4) happens-before every subsequent read of that field.

But what about regular, good old non-volatile fields? I've seen a lot of multi-threaded code that doesn't bother creating any sort of memory barrier after object construction with non-volatile fields. But I've never seen or heard of any issues because of it and I wasn't able to recreate such partial construction myself.

Do modern JVMs just put memory barriers after construction? Avoid reordering around construction? Or was I just lucky? If it's the latter, is it possible to write code that reproduces partial construction at will?

Edit:

To clarify, I'm talking about the following situation. Say we have a class:

public class Foo{
    public int bar = 0;

    public Foo(){
        this.bar = 5;
    }
    ...
}

And some Thread T1 instantiates a new Foo instance:

Foo myFoo = new Foo();

Then passes the instance to some other thread, which we'll call T2:

Thread t = new Thread(() -> {
     if (myFoo.bar == 5){
         ....
     }
});
t.start();

T1 performed two writes that are interesting to us:

  1. T1 wrote the value 5 to bar of the newly instantiated myFoo
  2. T1 wrote the reference to the newly created object to the myFoo variable

For T1, we get a guarantee that write #1 happened-before write #2:

Each action in a thread happens-before every action in that thread that comes later in the program's order.

But as far as T2 is concerned the Java memory model offers no such guarantee. Nothing prevents it from seeing the writes in the opposite order. So it could see a fully built Foo object, but with the bar field equal to equal to 0.

Edit2:

I took a second look at the example above a few months after writing it. And that code is actually guaranteed to work correctly since T2 was started after T1's writes. That makes it an incorrect example for the question I wanted to ask. The fix it to assume that T2 is already running when T1 is performing the write. Say T2 is reading myFoo in a loop, like so:

Foo myFoo = null;
Thread t2 = new Thread(() -> {
     for (;;) {
         if (myFoo != null && myFoo.bar == 5){
             ...
         }
         ...
     }
});
t2.start();
myFoo = new Foo(); //The creation of Foo happens after t2 is already running
Cœur
  • 37,241
  • 25
  • 195
  • 267
Malt
  • 28,965
  • 9
  • 65
  • 105
  • 1
    I did not notice any fences or `lock` prefixed instruction neither in case of a class contains only one final field nor in case a class contains only one non-final field. Looks a bit confusing to me... – St.Antario Aug 05 '18 at 16:08
  • On the real what prevents reordering of final fields initialization? – St.Antario Aug 05 '18 at 16:11
  • 1
    They're quite explicit about the guaranteed safety of `final` fields. I don't believe it would be necessary if the same rules applied for the construction of *all* fields. On the subject of hard-to-reproduce conurrency issues, don't hold your breath. Even documented problems can be seemingly impossible to reproduce, as expressed with [this question](https://stackoverflow.com/questions/50655856/concurrency-object-creation-in-java/). – Vince Aug 05 '18 at 16:44
  • 1
    @VinceEmigh I don't think that the authors of that piece of documentation are being coy with us. Clearly, there are no guarantees about regular fields. But anecdotal evidence suggests that it doesn't happen in practice. So maybe someone more knowledgeable in the JVM might be able to shed some light on this. After all, this isn't nitpicking. If such object construction is in fact unsafe in practice, a lot of code has to be fixed. – Malt Aug 05 '18 at 17:06
  • @Malt It's not an attempt to be coy, rather they didn't need to add that specific statement for the JLS to remain valid. Check out my answer. Let me know if you'd like me to compile some references from the spec which explains the behavior which should be expected. It's not specific to this, however the behavior for this *is* technically documented. – Vince Aug 06 '18 at 03:36
  • Found a "duplicate": https://stackoverflow.com/a/14457391/3080094 – vanOekel Aug 06 '18 at 17:26
  • @vanOekel yeah, seems like a very similar question. – Malt Aug 06 '18 at 17:28

3 Answers3

3

But anecdotal evidence suggests that it doesn't happen in practice

To see this issue, you have to avoid using any memory barriers. e.g. if you use thread safe collection of any kind or some System.out.println can prevent the problem occurring.

I have seen a problem with this before though a simple test I just wrote for Java 8 update 161 on x64 didn't show this problem.

Peter Lawrey
  • 525,659
  • 79
  • 751
  • 1,130
  • 2
    Makes sense. I looked at the generated C2 code and see no mfences or `lock`ed instructions. Yet I could not catch the statements reordering in case of nonfinal fields. – St.Antario Aug 06 '18 at 07:51
  • Thanks, Peter. After reading some additional material about the memory model I'm starting to suspect that you're right. It's mostly luck. Helped by the fact that mutlithreaded code is full of "natural" memory barriers like stuff from `java.util.concurrent` and the occasional `System.out.println` . Do you happen to recall any details about where and when you've seen such a problem happen? What version of Java, what kind of CPU? OS? – Malt Aug 06 '18 at 17:02
3

It seems there is no synchronization during object construction.

The JLS doesn't permit it, nor was I able to produce any signs of it in code. However, it's possible to produce an opposition.

Running the following code:

public class Main {
    public static void main(String[] args) throws Exception {
        new Thread(() -> {
            while(true) {
                new Demo(1, 2);
            }
        }).start(); 
    }
}

class Demo {
    int d1, d2;

    Demo(int d1, int d2) {
        this.d1 = d1;   

        new Thread(() -> System.out.println(Demo.this.d1+" "+Demo.this.d2)).start();

        try {
            Thread.sleep(500);
        } catch(InterruptedException e) {
            e.printStackTrace();
        }

        this.d2 = d2;   
    }
}

The output would continuously show 1 0, proving that the created thread was able to access data of a partially created object.

However, if we synchronized this:

Demo(int d1, int d2) {
    synchronized(Demo.class) {
        this.d1 = d1;   

        new Thread(() -> {
            synchronized(Demo.class) {
                System.out.println(Demo.this.d1+" "+Demo.this.d2);
            }
        }).start();

        try {
            Thread.sleep(500);
        } catch(InterruptedException e) {
            e.printStackTrace();
        }

        this.d2 = d2;   
    }
}

The output is 1 2, showing that the newly created thread will in fact wait for a lock, opposed to the unsynchronized exampled.

Related: Why can't constructors be synchronized?

Vince
  • 14,470
  • 7
  • 39
  • 84
  • @GhostCat Much appreciated. JLS doesn't mention if constructors are synchronized by default, same with methods. There exists sections on method syncing, as well as a statement which expresses why the `synchronized` modifier can't be applied to constructors: ["*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.*"](https://docs.oracle.com/javase/specs/jls/se10/html/jls-8.html#jls-8.8.3) - Not normal, but possible – Vince Aug 06 '18 at 05:26
  • @VinceEmigh That really is a good answer, but to a slightly different question. It indeed proves that it's possible to access a partially created object while the constructor is still running by leaking `this` from the constructor. But it doesn't prove or disprove the existence of a memory barrier once construction is finished. There's a lot of code out there that depends on regular fields (non-final, non-volatile) being initialized in an object passed from another thread. Nothing in the JLS guarantees a happens-before relation in such cases. – Malt Aug 06 '18 at 08:13
  • @Malt Mind giving a code example? If T1 is creating the object, how would T2 attempt to initialize fields of that object without a reference to that object during construction? Without leaking the reference, there is no need for synchronization, as no other threads will have access *unless* you leak the reference during construction: "*it would lock the object under construction, **which is nornally not made available to other threads***" - Although syncing is possible, you shouldn't need synchronization under normal circumstances. This also implies construction is not synchornized. – Vince Aug 06 '18 at 16:18
  • @VinceEmigh Say class `Foo` has `int` member named `bar` which is initialized to `5` in the constructor. T1 creates an instance of `foo` named `myFoo` so it is guaranteed to see that `myFoo.bar = 5`. T1 gives the reference to `myFoo` to a different thread, T2. Nothing in the memory model guarantees that T2 will see that `bar=5` despite having a valid reference to a constructed object since the write to x happened to a regular field in a different thread. – Malt Aug 06 '18 at 16:33
  • @VinceEmigh After reading more about the memory model and reading the responses in this thread, I came to suspect it's mostly luck that I never came across this issue. It makes sense since multi threaded code is often full of various memory barriers in form of `System.out.println` or various utilities and collections from `java.util.concurrent` as Peter Lawrey noted. – Malt Aug 06 '18 at 16:49
  • @Malt Check out my edit. It's not luck, rather there's no way for T2 to access a reference of an object T1 created unless T1 assigned the obj to a shared variable, or if the reference leaked during construction. I already explained the latter, so I included aj example of the former. Hopefully this new edit clears things up. – Vince Aug 06 '18 at 16:49
  • @VinceEmigh "But it'll never print 1 0 like the reference leak demo" <-- This is exactly the point of the question. The memory model doesn't guarantee that, at all. The write to `Demo.d1` is a write to a regular field (not final/volatile) in a different thread. The memory model doesn't guarantee that it'll be visible even if the reference to `demo` is. – Malt Aug 06 '18 at 16:52
  • @Malt The write to `d1` occurs in `T1`. How would `T2` access this data? It would need a reference to the data. That reference is only made available after T1 assigns it to a shared variable. T1 assigns *after* construction. T2 can try to access the object, but may see `null`. You're saying that there's a chance it may not be null & print `1 0`, right? If so, you're suggesting compilers are re-ordering the field initialization with assigning the object to a shared variable. – Vince Aug 06 '18 at 16:56
  • @VinceEmigh T1 performs two writes - (A) It writes 1 to d1. And (B) it writes the reference to new Demo() to the demo field. These two operations are guaranteed to happen in that specific order only in T1. T2 can see (B) first, and (A) some time in the future. See here - https://docs.oracle.com/javase/specs/jls/se7/html/jls-17.html#jls-17.4 – Malt Aug 06 '18 at 17:07
  • @Malt I knew ordering occurs, but I was assuming the compiler wouldn't order the write to `demo` before a write to `d2`, as that may cause undesirable results in a single-threaded environment (which would disallow reordering). I opened notepad, worked it out, and I don't see why it wouldn't. As you said, sharing data like this is common, so modern compilers probably avoid reordering in this way, hence the inability to properly reproduce. However, you're right, the spec does not guarantee that T2 would see T1 fully constructed. – Vince Aug 06 '18 at 17:23
  • @VinceEmigh the same reordering can happen in a single threaded environment if there aren't any reads between the two writes. But since there aren't reads, it doesn't matter. Also, the visibility issue is not just a matter of instruction reordering by the compiler. The two writes could happen into two different CPU cache lines, and one could be flushed to main memory before the other regardless of instruction order. – Malt Aug 06 '18 at 17:34
  • 1
    @VinceEmigh see my answer https://stackoverflow.com/a/51808614/1059372 as there is an example of someone loosing a huge amount of money (I hope that is fictional) of violating the JLS. – Eugene Aug 12 '18 at 12:02
3

Taking your example as the question itself - the answer would be yes, that is entirely possible. The initialized fields are visible only to the constructing thread, like you quoted. This is called safe publication (but I bet you already knew about this).

The fact that you are not seeing that via experimentation is that AFAIK on x86 (being a strong memory model), stores are not re-ordered anyway, so unless JIT would re-ordered those stores that T1 did - you can't see that. But that is playing with fire, literately, this question and the follow-up (it's close to the same) here of a guy that (not sure if true) lost 12 milion of equipment

The JLS guarantees only a few ways to achieve the visibility. And it's not the other way around btw, the JLS will not say when this would break, it will say when it will work.

1) final field semantics

Notice how the example shows that each field has to be final - even if under the current implementation a single one would suffice, and there are two memory barriers inserted (when final(s) are used) after the constructor: LoadStore and StoreStore.

2) volatile fields (and implicitly AtomicXXX); I think this one does not need any explanations and it seems you quoted this.

3) Static initializers well, kind of should be obvious IMO

4) Some locking involved - this should be obvious too, happens-before rule...

Lii
  • 11,553
  • 8
  • 64
  • 88
Eugene
  • 117,005
  • 15
  • 201
  • 306
  • 1
    My answers are my babies, so removing one really hurts. But I think I have to agree with you... – GhostCat Aug 12 '18 at 15:22
  • 1
    @VinceEmigh this is literally taken from *Intel Software Development Manual; Vol 3A; 8.2.2* : `Writes to memory are not reordered with other writes... with some exceptions`; that is the reason that in my understanding all barriers except `StoreLoad` (or `lock addl`) are free on Intel. – Eugene Aug 13 '18 at 08:22
  • 1
    `javac` never reorders memory access. You likely meant the JIT compiler/optimizer here. – Holger Aug 13 '18 at 15:29