Reputation: 921
Given below is a java class using Bill Pugh singleton solution.
public class Singleton {
int nonVolatileVariable;
private static class SingletonHelper {
private static Singleton INSTANCE = new Singleton();
}
private Singleton() { }
public static Singleton getInstance() {
return SingletonHelper.INSTANCE;
}
public int getNonVolatileVariable() {
return nonVolatileVariable;
}
public void setNonVolatileVariable(int nonVolatileVariable) {
this.nonVolatileVariable= nonVolatileVariable;
}
}
I have read in many places that this approach is thread safe. So if I understand it correctly, then the singleton instance is created only once and all threads accessing the getInstance
method will receive the same instance of the class Singleton
. However I was wondering if the threads can locally cache the obtained singleton object. Can they? If yes then doesn't that would mean that each thread can change the instance field nonVolatileVariable
to different values which might create problems.
I know that there are other singleton creation methods such as enum singleton, but I am particularly interested in this singleton creation method.
So my question is that, is there a need to use the volatile keyword like
int volatile nonVolatileVariable;
to make sure that the singleton using this approach is truely thread safe? Or is it already truly thread safe? If so how?
Upvotes: 3
Views: 348
Reputation: 131346
So my question is that, is there a need to use the volatile keyword like int volatile nonVolatileVariable; to make sure that the singleton using this approach is truly thread safe? Or is it already truly thread safe? If so how?
The singleton pattern ensures that a single instance of the class is created. It doesn't ensures that fields and methods be thread-safe and volatile doesn't ensure it either.
However I was wondering if the threads can locally cache the obtained singleton object ?
According to the memory model in Java, yes they can.
if yes then doesn't that would mean that each thread can change the instance field nonVolatileVariable to different values which might create problems.
Indeed but you would still a problem of consistency with a volatile
variable because volatile
handles the memory visibility question but it doesn't handle the synchronization between threads.
Try the following code where multiple threads increment a volatile int
100
times.
You will see that you could not get 100
at each time as result.
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
public class Singleton {
volatile int volatileInt;
private static class SingletonHelper {
private static Singleton INSTANCE = new Singleton();
}
private Singleton() {
}
public static Singleton getInstance() {
return SingletonHelper.INSTANCE;
}
public int getVolatileInt() {
return volatileInt;
}
public void setVolatileInt(int volatileInt ) {
this.volatileInt = volatileInt ;
}
public static void main(String[] args) throws InterruptedException {
ExecutorService executorService = Executors.newFixedThreadPool(5);
List<Callable<Void>> callables = IntStream.range(0, 100)
.mapToObj(i -> {
Callable<Void> callable = () -> {
Singleton.getInstance().setVolatileInt(Singleton.getInstance().getVolatileInt()+1);
return null;
};
return callable;
})
.collect(Collectors.toList());
executorService.invokeAll(callables);
System.out.println(Singleton.getInstance().getVolatileInt());
}
}
To ensure that each thread takes into consideration other invocations, you have to use external synchronization and in this case make the variable volatile is not required.
For example :
synchronized (Singleton.getInstance()) {
Singleton.getInstance()
.setVolatileInt(Singleton.getInstance().getVolatileInt() + 1);
}
And in this case, volatile is not required any longer.
Upvotes: 2
Reputation: 37845
The specific guarantee of this type of singleton basically works like this:
(This is documented in §12.4.2.)
In other words, what's guaranteed here is that all threads must at least see the effects of the assignment in private static Singleton INSTANCE = new Singleton();
, and anything else that was performed during static initialization of the SingletonHelper
class.
Your analysis that concurrent reads and writes of the non-volatile
variable can be inconsistent between threads is correct, although the language specification isn't written in terms of caching. The way the language specification is written is that reads and writes can appear out of order. For example, suppose the following sequence of events, listed chronologically:
nonVolatileVariable is 0
ThreadA sets nonVolatileVariable to 1
ThreadB reads nonVolatileVariable (what value should it see?)
The language specification allows ThreadB
to see the value 0 when it reads nonVolatileVariable
, which is as if the events had happened in the following order:
nonVolatileVariable is 0
ThreadB reads nonVolatileVariable (and sees 0)
ThreadA sets nonVolatileVariable to 1
In practice, this is due to caching, but the language specification doesn't say what may or may not be cached (except here and here, as a brief mention), it only specifies the order of events.
One extra note regarding thread-safety: some actions are always considered atomic, such as reads and writes of object references (§17.7), so there are some cases where the use of a non-volatile
variable can be considered thread-safe, but it depends on what you're specifically doing with it. There can still be memory inconsistency but concurrent reads and writes can't somehow interleave, so you can't end up with e.g. an invalid pointer value somehow. It's therefore sometimes safe to use non-volatile
variables for e.g. lazily-initialized fields if it doesn't matter that the initialization procedure could happen more than once. I know of at least one place in the JDK where this is used, in java.lang.reflect.Field
(also see this comment in the file), but it's not the norm.
Upvotes: 1