It is not possible, despite the fact that you have a data race.
The data race is because your gets and sets around o aren't synchronized, which means there's no happens-before order with them. You could solve that either by having both methods be synchronized, or by making o volatile, or in a few other ways.
Absent synchronization, the JVM is allowed to reorder events as seen by other threads. From Thread1's perspective, you have the following events (with methods inlined for simplicity):
c.o = null; // initial value
o1 = c.o;
o2 = c.o;
c.o = new Object(); // from Thread2
Luckily for you, there are two restrictions that make this work:
- The
c.c = null happens-before all other actions (see JLS 17.4.5).
- From a given thread's perspective, actions that happen on that thread always happen in the same order they appear in the code (also JLS 17.4.5). So for Thread1,
o1 = c.o happens-before o2 = c.o. (Thread2 would not have to see those reads in that order... but it never sees o1 or o2 at all, so there's no problem there.)
The first of those means that we can't take the c.o = null action and order it after c.c = new Object(). The second means that the reordering you mention at the bottom of your post is not allowed, from Thread1's perspective (and of course, Thread1 is the only thread that sees anything about o1 or o2).
Combining those two restrictions, we can see that if Thread1 ever sees c.o be non-null, then it will never see it revert back to null again. If o1 is non-null, then so must o2 be.
Note that this is pretty fickle. For instance, let's say that rather than only setting c.o once, Thread2 set it twice:
c.set("one");
c.set("two");
In that case, it would be possible to see o1 be "two" while o2 is "one". That's because the operations there are:
c.o = null; // initial value
o1 = c.o;
o2 = c.o;
c.c = "one"; // from Thread2
c.c = "two"; // from Thread2
The JVM can reorder the items from Thread2 however it sees fit, so long as they don't come before that c.c = null. In particular, this is valid:
c.o = null; // initial value
c.c = "two"; // from Thread2
o1 = c.o;
c.c = "one"; // from Thread2
o2 = c.o;
Removing the data race, by synchronizing the gets and sets to o, will fix that.
get()ever returns anything except fornull.ois never initialized andset()only modifiesoif the argument isnull