NOTE

Singleton Pattern

A class has one and only one instance, with Java and Go implementations including eager, lazy, DCL, static-inner-class, enum, mutex, and sync.Once approaches.

Software Architecture & EngineeringCreated Updated 1 min readhistorical

This is a historical learning note and may contain outdated or incomplete understanding.

A class has one and only one instance object.

1. UML

PlantUML 图表

2. Java

2.1. Eager Initialization

  • MySingleton
public class MySingleton
{
    // Create the object as soon as the JVM loads this class
    private static MySingleton singleton = new MySingleton();

    // Cannot be instantiated externally
    private MySingleton()
    {}

    // The instance can only be obtained through this method
    public static MySingleton getSingleton()
    {
        return singleton;
    }
}
  • Client
public class Client
{
    public static void main(String[] args)
    {
        MySingleton singleton = MySingleton.getSingleton();

        MySingleton singleton2 = MySingleton.getSingleton();

        // The same object instance
        System.out.println(singleton == singleton2);
    }
}
  • Characteristics
    • It is initialized as soon as the class is loaded, even if the instance is never used.

2.2. Lazy Initialization: Lock the Entire Method

public class MySingleton
{
    private static MySingleton singleton = null;
    // Cannot be instantiated externally
    private MySingleton()
    {}

    public static synchronized MySingleton getInstance() {// Must use synchronized to ensure thread safety
        if (instance == null) {
            instance = new MySingleton();
        }
        return instance;
    }
}
  • Characteristics
    • The instance is created only when it is actually used.
    • Every call has to acquire the lock, although the lock is actually needed only during the first creation.

2.3. Lazy Initialization: Double-Checked Locking / DCL

2.3.1. First Version: synchronized Block Only

public class MySingleton
{
    private static MySingleton singleton = null;
    // Cannot be instantiated externally
    private MySingleton()
    {}

    public static MySingleton getSingleton() {
        if (instance == null) { // Single Checked: multiple threads may enter here at the same time
            synchronized (MySingleton.class) {
                instance = new MySingleton();
            }
        }
        return instance ;
    }
}

2.3.2. Second Version: synchronized Block + Double Check

public class MySingleton
{
    private static MySingleton singleton = null;
    // Cannot be instantiated externally
    private MySingleton()
    {}

    public static MySingleton getSingleton() {
        if (instance == null) {                         //Single Checked
            synchronized (MySingleton.class) {
                if (instance == null) {                 //Double Checked
                    instance = new MySingleton();
                }
            }
        }
        return instance ;
    }
}

instance = new MySingleton() has a problem. This statement is divided into three steps:

  1. Allocate memory.
  2. Call the Singleton constructor to initialize member variables.
  3. Make the instance reference point to the allocated memory space.

The JIT may reorder instructions. Suppose thread 1 executes in the order 1-3-2, while thread 2 executes 1-2-3. When thread 1 has executed step 3, thread 2 may obtain the singleton before initialization has finished, causing an error when it is used.

  • Solution
    Add volatile and use its ordering guarantee.

2.3.3. Third Version: synchronized Block + Double Check + volatile

public class MySingleton {
    private volatile static MySingleton instance; // Declare as volatile
    private MySingleton (){}

    public static Singleton getSingleton() {
        if (instance == null) {
            synchronized (MySingleton.class) {
                if (instance == null) {
                    instance = new MySingleton();
                }
            }
        }
        return instance;
    }
}

According to volatile’s happens-before rule, a volatile write happens-before a subsequent volatile read.

2.4. Static Inner Class

public final class MySingleton
{
    private static class SingletonHolder
    {
        private static final MySingleton INSTANCE = new MySingleton();
    }

    private MySingleton()
    {
    }

    public static MySingleton getInstance()
    {
        return SingletonHolder.INSTANCE;
    }
}
  • Characteristics
    • Because SingletonHolder is private, there is no way to access it except through getInstance(), so this is lazy initialization.
    • If the inner class is not used when loading MySingleton, the inner class is not loaded, such as in a test method. It is loaded only when the static inner class is used, such as by getInstance.

2.5. Enum

public enum MySingleton
{
    SINGLETON(10);
    private Integer val;

    MySingleton(Integer val)
    {
        this.val = val;
    }

    public static void main(String[] args)
    {
        System.out.println(MySingleton.SINGLETON == MySingleton.SINGLETON);//true
    }
}

3. Golang

3.1. Not Thread-Safe

  • singleton
type singleton struct {
}

//private
var instance *singleton

//public
// Not thread-safe
func GetInstance() *singleton {
	if instance == nil {
		instance = &singleton{}
	}
	return instance
}
  • client
func main() {
	instance := 单例.GetInstance()
	instance2 := 单例.GetInstance()

	fmt.Println(instance == instance2)
}

3.2. Thread-Safe

// Thread-safe: use a lock
var mutex sync.Mutex

func GetInstance2() *singleton {

	mutex.Lock()
	defer mutex.Unlock()
	if instance == nil {
		instance = &singleton{}
	}
	return instance
}

3.3. Thread-Safe DCL

//DCL
func GetInstance3() *singleton {
	if instance == nil {
		mutex.Lock()
		defer mutex.Unlock()
		if instance == nil {
			instance = &singleton{}
		}
	}
	return instance
}

3.4. sync.Once

// Execute only once
var once sync.Once

func GetInstance4() *singleton {
	once.Do(func() {
		instance = &singleton{}
	})
	return instance
}

4. References

Discussion

Sign in with GitHub to comment. Discussions are stored as GitHub Issues.View on GitHub