views:

590

answers:

4

IMPORTANT EDIT I know about the "happens before" in the thread where the two assignments are happening my question is would it be possible for another thread to be reading "b" non-null while "a" is still null. So I know that if you're calling doIt() from the same thread as the one where you previously called setBothNonNull(...) then it cannot throw a NullPointerException. But what if one is calling doIt() from another thread than the one calling setBothNonNull(...) ?

Note that this question is solely about the volatile keyword and the volatile guarantees: it is not about the synchronized keyword (so please don't answer "you must use synchronize" for I don't have any issue to solve: I simply want to understand the volatile guarantees (or lack of guarantees) regarding out-of-order execution).

Say we have an object containing two volatile String references that are initialized to null by the constructor and that we have only one way to modify the two String: by calling setBoth(...) and that we can only set their references afterwards to non-null reference (only the constructor is allowed to set them to null).

For example (it's just an example, there's no question yet):

public class SO {

    private volatile String a;
    private volatile String b;

    public SO() {
        a = null;
        b = null;
    }

    public void setBothNonNull( @NotNull final String one, @NotNull final String two ) {
        a = one;
        b = two;
    }

    public String getA() {
        return a;
    }

    public String getB() {
        return b;
    }

}

In setBothNoNull(...), the line assigning the non-null parameter "a" appears before the line assigning the non-null parameter "b".

Then if I do this (once again, there's no question, the question is coming next):

doIt() {
    if ( so.getB() != null ) {
        System.out.println( so.getA().length );
    }
}

Am I correct in my understanding that due to out-of-order execution I can get a NullPointerException?

In other words: there's no guarantee that because I read a non-null "b" I'll read a non-null "a"?

Because due to out-of-order (multi)processor and the way volatile works "b" could be assigned before "a"?

volatile guarantees that reads subsequent to a write shall always see the last written value, but here there's an out-of-order "issue" right? (once again, the "issue" is made on purpose to try to understand the semantics of the volatile keyword and the Java Memory Model, not to solve a problem).

+2  A: 

Am I correct in my understanding that due to out-of-order execution I can get a NullPointerException? In other words: there's no guarantee that because I read a non-null "b" I'll read a non-null "a"?

Assuming that the values assigned to a and b or non-null, I think your understanding is not correct. The JLS says this:

(1) If x and y are actions of the same thread and x comes before y in program order, then hb(x, y).

(2) If an action x synchronizes-with a following action y, then we also have hb(x, y).

(3) If hb(x, y) and hb(y, z), then hb(x, z).

and

(4) A write to a volatile variable (§8.3.1.4) v synchronizes-with all subsequent reads of v by any thread (where subsequent is defined according to the synchronization order).

Theorem

Given that thread #1 has called setBoth(...); once, and that the arguments were non-null, and that thread #2 has observed b to be non-null, then thread #2 cannot then observe a to be null.

Informal Proof

  1. By (1) - hb(write(a, non-null), write(b, non-null)) in thread #1
  2. By (2) and (4) - hb(write(b, non-null), read(b, non-null))
  3. By (1) - hb(read(b, non-null), read(a, XXX)) in thread #2,
  4. By (4) - hb(write(a, non-null), read(b, non-null))
  5. By (4) - hb(write(a, non-null), read(a, XXX))

In other words, the write of a non-null value to a "happens-before" the read of the value (XXX) of a. The only way that XXX can be null, is if there was some other action writing null to a such that hb(write(a,non-null), write(a,XXX)) and hb(write(a,XXX), read(a,XXX)). And this is impossible according to the problem definition, and therefore XXX cannot be null. QED.

Explanation - the JLS states that the hb(...) ("happens-before") relationship does not totally forbid reordering. However, if hb(xx,yy), then reordering of actions xx and yy is only allowed if the resulting code has the same observable effect as the original sequence.

Stephen C
@Stephen C: I should have called it *setBothNonNull* but setting first argument to null cannot happen because @NotNull is guaranteed to throw an exception (IntelliJ IDEA user since years and I started using @NotNull as soon as it was available for IntelliJ, a long time before it came to Eclipse).
Webinator
@Stephen C: *"If x and y are actions of the same thread..."* but my question is precisely if *another* thread reads the volatiles. Reading your answer you make it sound like out-of-order execution is a non-existing concept in Java, which I don't think is true. If I were to do my non-null/null test *from the same thread where I do the assignment*, then I *know* I cannot have an NPE. My question is what if I'm doing this test from *another* thread?
Webinator
@Stephen C: +1... I really like the very clean edit to your question. I'll just now have to grasp the nitty details of the "happens-before".
Webinator
A: 

I read this page and found a non-volatile & non-synchronized version of your question:

class Simple {
    int a = 1, b = 2;
    void to() {
        a = 3;
        b = 4;
    }
    void fro() {
        System.out.println("a= " + a + ", b=" + b);
    }
}

fro may obtain either 1 or 3 for the value of a, and independently may obtain either 2 or 4 for the value of b.

(I realize this doesn't answer your question but it complements it.)

kiwicptn
Note that the page you quote is from the second edition of the Java Language Specification, which has since been superseded by the third edition, which no longer has the section you quote.
Avi
@kiwicptn: thanks for the yup but as Avi wrote, the Java Memory Model changed since then (that's why now the double-checked locking works if you're using a volatile while it didn't previously, for example).
Webinator
+6  A: 

No, you will never get a NPE. This is because volatile also has the memory-effect of introducing a happens-before relationship. In other words, it will prevent reordering of

a = one;
b = two;

The statements above, will not be re-ordered, and all threads will observe value one for a if b already has value two.

Here is a thread in which David Holmes explains this:
http://markmail.org/message/j7omtqqh6ypwshfv#query:+page:1+mid:34dnnukruu23ywzy+state:results

EDIT (response to the follow-up): What Holmes is saying is, the compiler could in theory do a reorder if there were only thread A. However, there ARE other threads, and they CAN detect the reordering. That is why the compiler is NOT allowed to do that reordering. The java memory model requires the compiler specifically to make sure that no thread will ever detect such reordering.

But what if one is calling doIt() from another thread than the one calling setBothNonNull(...) ?

No, you will still NEVER have a NPE. volatile semantics do impose inter-thread ordering. Meaning that, for all existing thread, assignment of one happens before the assignment of two.

Enno Shioji
Well what David Holmes wrote is even more confusing for it says: *"As you say, a thread calling methodA() can't tell if anything inside methodA() gets reordered, but another thread can tell."* So how could another thread tell it there's has been a re-ordering if, in my example, I cannot read b non-null and a null? (that would be, in my example, the only way to detect that there has been re-ordering taking place).
Webinator
Hi I added my response to the original answer.
Enno Shioji
@Zwei steinen: Thanks a lot for the follow up, I'll accept your answer but first I'll leave other people comment/answer if anyone wants to add something, thanks a lot for the taking the time to explain this to me :)
Webinator
@WizardOfOdds: NP :) In the future, you might want to direct your questions to http://altair.cs.oswego.edu/mailman/listinfo/concurrency-interestThis is the mailing list where bunch of JSR166 people frequent, and they are extremely friendly and helpful. And of course, they know java concurrency literary inside-out.
Enno Shioji
+2  A: 

I found the following post that explains that volatile has the same ordering semantics as synchronized in this case. Java Volatile is Powerful

Brandon Bodnár
@Brandon Bodnar: thanks for that link... Sadly the article explains doesn't explain much about re-ordering. It says that re-ordering is guaranteed in a unique thread, but it goes on to give an example where threadTwo() can read "b" *true* and "a" *false*. Which is exactly the same as my non-null/null example. It is not clear at all if it's just an example and if volatile fixes that or not.
Webinator