You have a serious miss-understanding what volatile
does, but to be fair the internet and stackoverflow including is just polluted with wrong or incomplete answers about this. I also admit that I think I have a good grab about it, but sometimes have to re-read some things again.
What you have there shown - is called the "double check locking" idiom and it's a perfectly valid use-case to create a singleton. The question is if you really need it in your case (the other answer has shown a far more simple way, or you can read the "enum singleton pattern" too if you want). It's a bit funny how many people know that volatile
is needed for this idiom, but can't really tell why it is needed.
DCL is doing two things mainly - ensures atomicity (multiple threads can't not enter the synchronized block at the same time) and ensures that once created, all threads will see that created instance, called visibility. At the same time, it ensures that the synchronized block will be entered a single time, all threads after that will not need to do that.
You could have easily done it via:
private Singleton instance;
public Singleton get() {
synchronized (this) {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
But now every single Thread that needs that instance
has to compete for the lock and has to enter that synchronized block.
Some people think that: "hey, I can work around that!" and write (thus enter the synchronized block only once):
private Singleton instance; // no volatile
public Singleton get() {
if (instance == null) {
synchronized (this) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
As simple as that is - that is broken. And this isn't easy to explain.
it is broken because there are two independent reads of instance
; JMM allow for these to be re-ordered; thus it is entirely valid that if (instance == null)
does not see a null; while return instance;
sees and returns a null
. Yes, this is counter-intuitive, but entirely valid and provable (I can write a jcstress
test to prove this in 15 minutes).
the second point is a bit more tricky. Suppose your singleton has a field that you need to set.
Look at this example:
static class Singleton {
private Object some;
public Object getSome() {
return some;
}
public void setSome(Object some) {
this.some = some;
}
}
And you write code like this to provide that singleton:
private Singleton instance;
public Singleton get() {
if (instance == null) {
synchronized (this) {
if (instance == null) {
instance = new Singleton();
instance.setSome(new Object());
}
}
}
return instance;
}
Since the write to the volatile
(instance = new Singleton();
) happens before setting the field that you need instance.setSome(new Object());
; some Thread that reads this instance might see that instance
is not null, but when doing instance.getSome()
will see a null. The correct way to do this would be (plus making the instance volatile
):
public Singleton get() {
if (instance == null) {
synchronized (this) {
if (instance == null) {
Singleton copy = new Singleton();
copy.setSome(new Object());
instance = copy;
}
}
}
return instance;
}
Thus volatile here is needed for safe publication; so that the published reference is "safely" seen by all threads - all it's fields are initialized. There are some other ways to safely publish a reference, like final
set in the constructor, etc.
Fact of life: reads are cheaper than writes; you should not care what volatile
reads do under the hood as long as your code is correct; so don't worry about "reads from main memory" (or even better don't use this phrase without even partially understanding it).