Deepak Shajan
Deepak Shajan

Reputation: 921

Use of volatile in Singleton(Bill Pughs Solution)

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

Answers (2)

davidxxx
davidxxx

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

Radiodef
Radiodef

Reputation: 37845

The specific guarantee of this type of singleton basically works like this:

  1. Each class has a unique lock for class initialization.
  2. Any action which could cause class initialization (such as accessing a static method) is required to first acquire this lock, check if the class needs to be initialized and, if so, initialize it.
  3. If the JVM can determine that the class is already initialized and the current thread can see the effect of that, then it can skip step 2, including skipping acquiring the lock.

(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

Related Questions