Suppose we are instantiating a singleton using double-checked locking:
public static Instance getInstance() {
if (this.instance == null) {
synchronized(Instance.class) {
if (this.instance == null) {
this.instance = new Instance();
}
}
}
return this.instance;
}
The question is in the semantics of the program if instance
variable would be volatile and double-checked locking would be removed.
private volatile static Instance instance;
public static Instance getInstance() {
if (this.instance == null) {
this.instance = new Instance();
}
return this.instance;
}
Will the class get instantiated only once? Or, put another way, can volatile reads clash in such a way that two threads will see null
value of the reference and double instantiation will be performed?
I am aware of the happens-before relationship between volatile write and volatile read, and that volatile forbids caching (so all reads and writes will be executed in main memory, not in processor's cache), but it isn't clear in the case of concurrent volatile reads.
P.S.: the question is not in applying of the Singleton pattern (it was just an example where the problem is clear), it is just about if double-checked locking can be replaced with volatile read - volatile write without program semantics change, nothing more than that.
Considering this code.
private volatile static Instance instance;
public static Instance getInstance() {
if (this.instance == null) {
this.instance = new Instance();
}
return this.instance;
}
From your question:
Will the class get instantiated only once? Can volatile reads clash in such way that two threads will see null value of the reference and double instantiation will be performed?
Volatile reads can't clash in such a way out of JMM guarantees. You can still end up with two instances though, if multiple threads swap after the if but before beginning to instantiate the volatile variable.
if (this.instance == null) {
// if threads swap out here you get multiple instances
this.instance = new Instance();
}
In order to ensure the above scenario does not happen, you have to use double checked locking
if (this.instance == null) {
// threads can swap out here (after first if but before synchronized)
synchronized(Instance.class) {
if (this.instance == null) {
// but only one thread will get here
this.instance = new Instance();
}
}
}
Note that there are two aspects that have to be considered here.
synchronized
block).volatile
declaration for the instance variable in order to leverage the JMM happens before guarantee).