As with many multithreading issues, it's a bit subtle. Allow me to dig into the question a bit, and then provide a kinda-answer.
Refining the question
If a client sees a non-null
reference, they'll only ever see the one Singleton
you create. Of that there's no question. Here are the two questions:
- is it possible for anyone to see
null
?
- is it possible for someone to see a partially constructed
Singleton
?
The latter question isn't quite relevant to your case, because the Singleton
doesn't have any state. But if it did have state, and that state was stored in non-final
fields, then it would be an issue. For instance, given:
public class Singleton {
private /* non-final */ String name;
private Singleton(String name) {
this.name = name;
}
}
... a partially-constructed Singleton
is one whose name is null
(despite having been set to a non-null
value at construction time).
What you would want is probably (a) that nobody sees null
and (b) that nobody sees a partially-constructed object. To get that, you need the class to be free of data races. To do that, you need a happens-before relationship.
So the question is really: is there a happens-before relationship between the thread that initializes instance
, and any thread that later reads it?
So, what's the answer?
The JLS is a bit iffy on this. There's a detailed description of class initialization at JLS 12.4.2 which includes locking on the class, and thus introduces a happens-before edge. But there's nothing in the JLS to specify what happens when a class is already initialized! In those cases, the JLS doesn't require any lock, and therefore doesn't establish any happens-before relationship. A strict reading of the JLS would suggest that clients in other threads could see a null
reference or partially-constructed object.
The JLS hints that this shouldn't happen in 12.4.1:
The intent is that a class or interface type has a set of initializers that put it in a consistent state, and that this state is the first state that is observed by other classes.
Well, that's great that this is "the intent," but there's nothing (other than that statement of intent) to require it.
A final
field (static or not) gets special thread safety semantics (JLS 17.5, or see this question on SO) that essentially provides the aforementioned happens-before edge, and thus removes the data race and ensures a non-null
reference to a fully-constructed object.