The use of volatile in singleton mode

Posted by Alk3m1st on Tue, 04 Jan 2022 07:08:01 +0100

Singleton mode

The common writing methods of single case mode include lazy mode, hungry mode, double check mode, etc.

  • Lazy mode is to create objects when you use them.
  • Hungry man mode is a static object that has been loaded in advance.
  • Double check mode is to check twice before and after locking to prevent multiple threads from creating multiple objects.

The singleton mode has the following characteristics:

  1. A singleton class can only have one instance.
  2. A singleton class must create its own unique instance.
  3. A singleton class must provide this instance to all other objects.

Advantages: objects will not be created and destroyed frequently, wasting system resources

Singleton mode in the case of single thread

class Singleton {
    private static Singleton instance = null;

    private Singleton() {
        System.out.println("I am the construction method" + Thread.currentThread().getName());
    }

    static Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }

    public static void main(String[] args) {
        Singleton instance1 = Singleton.getInstance();
        Singleton instance2 = Singleton.getInstance();
        Singleton instance3 = Singleton.getInstance();
        System.out.println(instance1 == instance2);
        System.out.println(instance2 == instance3);

Operation results:

I am the construction method main

true

true

Singleton mode in multithreading

It can be seen that in the case of single thread, this writing method meets the requirements of singleton mode, and the construction method is called only once.

So let's try again in a multithreaded environment

class Singleton {
    private static Singleton instance = null;

    private Singleton() {
        System.out.println("I am the construction method" + Thread.currentThread().getName());
    }

    static Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }

    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                Singleton.getInstance();
            }, String.valueOf(i)).start();
        }
    }
}

Operation results:

I am constructor 0
 I am construction method 4
 I am construction method 3
 I am construction method 2
 I am construction method 1

This is because in the case of multithreading, when the instance object has not been created, multiple threads enter the code for creating the object (because they judge that instance == null at this time), so multiple instances are created from new

Of course, this obviously does not meet the requirements of the singleton mode, so we introduce the singleton mode in the double ended check lock mode

Singleton mode in DDL mode

  • D:Double
  • C:Check
  • L:Lock

Looking at the following code, we can see that instance ==null is judged before and after locking, so it is called double end locking.

class SingletonDCL {
    private static SingletonDCL instance = null;

    private SingletonDCL() {
        System.out.println("I am the construction method" + Thread.currentThread().getName());
    }

    static SingletonDCL getInstance() {
        if (instance == null) {
            synchronized (Singleton.class) {
                if (instance == null) {
                    instance = new SingletonDCL();
                }
            }
        }
        return instance;
    }

    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                SingletonDCL.getInstance();
            }, String.valueOf(i)).start();
        }
    }
}

Operation results:

I am constructor 0

We can see that the running result is what we want. Only one instance is created in a multithreaded environment. Although the double ended check lock greatly reduces the probability of creating multiple instances, there are still some problems.

Existing shortcomings

When a thread reads that instance is not empty, the reference object of instance may not have completed initialization, which is due to instruction rearrangement.

instance = new SingletonDCL(); This process can be divided into the following three steps:

  • memory = allocate(); //1. Allocate object memory space
  • instance(memory);//2. Initialization object
  • instance = memory;//3. Set instance to point to the memory address of the object just allocated. At this time, instance= null;

However, in a single thread environment, there is no data dependency between 2 and 3, so instruction rearrangement may occur,

  • memory = allocate(); //1. Allocate object memory space
  • instance = memory;//3. Set instance to point to the memory address of the object just allocated. At this time, instance=
    null, but the object has not been initialized yet.
  • instance(memory);//2. Initialization object

So if a thread accesses instance at this time= Null, because the object has not been initialized, it will return a null value, which causes thread safety problems. Therefore, we need to add volatile to prevent instruction rearrangement

private volatile static SingletonDCL instance = null;

summary

Safe singleton mode in multithreaded environment = double ended check lock + volatile

Of course, synchronized can also be added to the method, but this locks the whole method and reduces the performance. It is not recommended.

Topics: Java Programming jvm Design Pattern Multithreading