Reputation: 4365
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.
Upvotes: 1
Views: 100
Reputation: 6414
Yes, indeed: volatile reads can clash in such way that two threads will see null value of the reference and double instantiation will be performed.
You need double brace initialization and volatile as well.
That's because when instance
becomes non null, you don't synchronize other threads on anything before reading it - first if
just makes them go further to return the unsynchronized
value (even if the initializing thread doesn't escape the synchronized block yet), this could result in a subsequent thread reading not initialized variable because of the lack of synchroniaztion. Synchronization to work properly needs to be performed by every thread accessing the data governed by it, the DCL omits the synchronization after initialization which is wrong. That's why you need additionaly volatile for the DCL to work, then the volatile will ensure that you read initialized value.
There is no such thing as processor cache separation, reads and writes are visible right away, but there are instruction rearrangements, so in favor for optimization processor can invoke some instructions later in time if it doesn't need their results right away. The whole point of synchronization and volatile is to not rearrange the order of instructions for threads accesing them. That way if something is synchronized and is stated as done in code, it is really done and other threads can access it safely. That's the whole point of happens before guarantee.
Summing this together: without proper synchronization processor can initialize the reference to the instance
to be non null, but instance
could be not fully initialized internally, so subsequent thread reading it could read uninitialized object and behave wrongly because of that.
Upvotes: 2
Reputation: 5486
Without synchronization, your code is definitely broken, because 2 threads may see that the value of instance is null, and both will perform an initialization (consider a context switch at every line, and see what happens.
Beside that, even with synchronization double checked locking (DCL) was considered broken in Java in the past because when running unsynchronized, a second thread may experience the initialization operations in a different order. You may fix your code by adding a local variable and load the volatile into it whenever you want to read it:
public static Instance getInstance() {
Instance tmp = instance;
if (tmp == null) {
synchronized(Instance.class) {
Instance tmp = instance;
if (tmp == null) {
instance = new Instance();
}
}
}
return instance;
}
but a safer solution would be using the ClassLoader as your synchronization mechanism, and will also allow you to stop using the slow volatile access every time you access the singleton:
public class Instance {
private static class Lazy {
private static Instance INSTANCE = new Instance();
}
public static Instance getInstance() {
return Lazy.INSTANCE;
}
}
INSTANCE
will be initialized only when the first thread enters getInstance()
Upvotes: 2
Reputation: 28036
Use an enum if you really need a singleton in Java. It fixes these things for you:
enum MySingleton {
INSTANCE;
public static MySingleton getInstance() {
return INSTANCE;
}
}
The JVM will initialize the instance in a thread safe manner on the first access to MySingleton
. The only way to get more than one instance of it, is if you have multiple classloaders.
Upvotes: 0
Reputation:
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). Upvotes: 1