Volatile Double Locking Singleton Object

Singleton class has Thread Safe Double Checked Locking to make sure the instance should be initialized only once, but before java 1.5, the Double Checked Locking was failing. Java Compiler tries to optimize the execution. Such as caching data on CUP cache or reordering execution. to understand better consider the below situation.

  • The 1st execution cycle, thread A checks for Objects.isNull(appleInstance), currently, object is null so it gets true.
  • The 2nd execution cycle, the Thread A processor assigns the space in heap memory for the object.
  • The 3rd execution cycle, Thread A processor creates a reference of the object in the stack memory location and points to it.
  • The 4th execution cycle, Thread B checks, for instance, nullability Objects.isNull(appleInstance), it gets returns false hence Thread B will take the object, but Thread A has still not updated the value in the object so currently object is created but the value is not present.
  • The 5th cycle, Thread A assigns the value in the instance variable. but till now Thread A has an invalid object.

The issue here is that Thread B has read the variable before Thread A was able to complete the writing, it is also called the happens-before relationship.

If Multiple write operations happen on a variable, to optimize the process, JVM copies the value of the instance variable from the main memory into the CPU cache, read from it, updates it and once all the updates are done, then writes back into main memory. meanwhile, another thread processed by another different CPU or core tried to read the variable.

package org.wesome.dsalgo.design.pattern.singleton;

import java.util.Objects;

public class Apple {
    private static Apple appleInstance;

    public Apple() {
        System.out.println("Apple default constructor");
    }

    public static Apple getAppleInstance() {
        System.out.println("Apple getAppleInstance");
        if (Objects.isNull(appleInstance)) {
            synchronized (Apple.class) {
                if (Objects.isNull(appleInstance)) {
                    appleInstance = new Apple();
                }
            }
        }
        return appleInstance;
    }
}
package org.wesome.dsalgo.design.pattern.singleton;

class AppleThread extends Thread {
    public void run() {
        Apple apple = Apple.getAppleInstance();
        System.out.println("Object created by " + Thread.currentThread().getName() + " is " + apple);
    }
}

public class SingletonClass {
    public static void main(String[] args) {
        AppleThread AppleThread1 = new AppleThread();
        AppleThread AppleThread2 = new AppleThread();
        AppleThread1.start();
        AppleThread2.start();
    }
}
plugins {
    id 'java'
    id "io.freefair.lombok" version "6.4.1"
}

group = 'org.wesome'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = JavaVersion.VERSION_1_8

configurations {
    compileOnly {
        extendsFrom annotationProcessor
    }
}

repositories {
    mavenCentral()
}

dependencies {
    testImplementation 'org.junit.jupiter:junit-jupiter:5.6.2'
}

test {
    useJUnitPlatform()
}

in java 1.5, a volatile variable was introduced, and volatile directors the compiler to always read from the main memory and write into the main memory and not from the CPU cache.

it guarantees that another thread will not see the half initialized value of another thread ie the happens-before relationship, the read will happen on the volatile variable only when all the write has successfully completed. 

After introduction of volatile in java 1.5, the Double Checked Locking was not failing.

Double Locked Singleton Object creation is antipattern and should be avoided. Bill Pugh Singleton Solution or Holder Singleton Pattern is advised

CPU cache was designed to improve performance while execution. forcing thread every time read and write into main memory and bypassing the CPU cache will be a performance hit.

package org.wesome.dsalgo.design.pattern.singleton;

import java.util.Objects;

public class Apple {
    private volatile static Apple appleInstance;

    public Apple() {
        System.out.println("Apple default constructor");
    }

    public static Apple getAppleInstance() {
        System.out.println("Apple getAppleInstance");
        if (Objects.isNull(appleInstance)) {
            synchronized (Apple.class) {
                if (Objects.isNull(appleInstance)) {
                    appleInstance = new Apple();
                }
            }
        }
        return appleInstance;
    }
}
package org.wesome.dsalgo.design.pattern.singleton;

class AppleThread extends Thread {
    public void run() {
        Apple apple = Apple.getAppleInstance();
        System.out.println("Object created by " + Thread.currentThread().getName() + " is " + apple);
    }
}

public class SingletonClass {
    public static void main(String[] args) {
        AppleThread AppleThread1 = new AppleThread();
        AppleThread AppleThread2 = new AppleThread();
        AppleThread1.start();
        AppleThread2.start();
    }
}
plugins {
    id 'java'
    id "io.freefair.lombok" version "6.4.1"
}

group = 'org.wesome'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = JavaVersion.VERSION_1_8

configurations {
    compileOnly {
        extendsFrom annotationProcessor
    }
}

repositories {
    mavenCentral()
}

dependencies {
    testImplementation 'org.junit.jupiter:junit-jupiter:5.6.2'
}

test {
    useJUnitPlatform()
}

follow us on