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.
This is a historical learning note and may contain outdated or incomplete understanding.
A class has one and only one instance object.
1. UML
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 ;
}
}
- Problem: instruction reordering.
Object Creation Process.md
instance = new MySingleton() has a problem. This statement is divided into three steps:
- Allocate memory.
- Call the
Singletonconstructor to initialize member variables. - Make the
instancereference 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
Addvolatileand 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
SingletonHolderis private, there is no way to access it except throughgetInstance(), 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 bygetInstance.
- Because
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
}
Discussion
Sign in with GitHub to comment. Discussions are stored as GitHub Issues.View on GitHub