Detailed explanation of single case mode

Keywords: Design Pattern

summary

Singleton mode: in the whole system, only one instance object of a class can be obtained and used

Key points:

  • Private constructor
  • Create object inside class
  • Provide static methods for obtaining instances externally

Hungry Han style

Common way

Hungry Chinese style: when the class is loaded, create the object directly, which is thread safe

public class Singleton {

    private static Singleton INSTANCE = new Singleton();

    private Singleton() {
        
    }

    public static Singleton getInstance() {
        return INSTANCE;
    }
}

Possible problems:

  • It may cause a waste of memory
  • It is not safe for reflection and deserialization

Enumerative

public enum Singleton {
    INSTANCE {
        @Override
        protected void method() {
            ...
        }
    }
    protected abstract void method();
}
public void test() {
    Singleton s = Singleton.INSTANCE;
}

It is safe for reflection and deserialization. Because it has no constructor, getting the constructor through reflection to create an object will throw an exception

Lazy style

Non thread safe

Lazy: load the object the first time you use it. Thread safety issues exist

Instead of creating objects when class 1 is loaded, there is randomness in concurrency

public class Singleton {

    private static Singleton INSTANCE = null;

    private Singleton() {
        
    }

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

synchronized

Use synchronized to achieve thread safe lazy. Low efficiency, improvement: double check lock

public class Singleton {

    private static Singleton INSTANCE = null;

    private Singleton() {
    }

    public static synchronized Singleton getInstance() {
        if (INSTANCE == null) {
            INSTANCE = new Singleton();
        }
        return INSTANCE;
    }
}
public class Singleton {

    private static Singleton INSTANCE = null;

    private Singleton() {
    }

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

Double check lock

public class Singleton {

    private static volatile Singleton INSTANCE = null;

    private Singleton() {
    }

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

Reduce the number of synchronizations. However, it is not 100% safe because there is instruction rearrangement (which may cause a thread to get an empty object). Using volatile prohibits instruction rearrangement

ThreadLocal

A space for time operation cannot guarantee the safety of multithreading

public class Singleton {

    private static Singleton INSTANCE = null;

    private Singleton() {
        
    }

    private static final ThreadLocal<Singleton> threadLocalSingleton = new ThreadLocal<Singleton>() {
        @Override
        protected Singleton initialValue() {
            return new Singleton();
        }
    } 
    public static Singleton getInstance() {
        return threadLocalSingleton.get();
    }
}

CAS

Lock free, optimistic strategy, thread safety

Disadvantages: many garbage objects will be generated, and may cause an endless loop

public class Singleton {
    private static final AtomicReference<Singleton> INSTANCE = new AtomicReference<>();
    private Singleton() {
        
    }
    
    public static final Singleton getInstance() {
        for (;;) {
            Singleton current = INSTANCE.get();
            if (current != null) return current;
            if (INSTANCE.compareAndSet(null, )) {
                return current;
            }
        }
    }
}

The difference between hungry and lazy

  • The creation time is different. The hungry type creates an object instance when the class is loaded, and the lazy type creates it when it is used
  • There is no thread safety problem for the hungry type, and there is thread safety problem for the lazy type
  • There is the possibility of wasting resources
  • java.lang.Runtime is a classic singleton pattern

Posted by mzfp2 on Fri, 19 Nov 2021 23:37:05 -0800