[design mode] single instance mode

Posted by ThinkGeekness on Fri, 14 Jan 2022 10:36:30 +0100

1. Definitions

  • Singleton Pattern refers to ensuring that a class has absolutely only one instance in any case and providing a global access point.
  • Hide all its construction methods
  • It belongs to creation mode

2. Applicable scenarios

Make sure there is absolutely only one instance in any case

For example: ServletContext, ServletConfig, ApplicationContext, DBPool

3. Common writing methods of singleton mode

3.1 starving singleton (create an instance when the singleton class is loaded for the first time)

advantage

High execution efficiency, high performance and no lock

shortcoming

Class is initialized when it is loaded. In some cases, it may cause a waste of memory, because it will be initialized whether it is used or not. What is not wasted is that it is initialized when it is used. Therefore, when it is used in large quantities, it will waste memory by using hungry man style single instance

Case code

/**
 * Advantages: high execution efficiency, high performance and no lock
 * Disadvantages: class is initialized when it is loaded, which may cause memory waste in some cases,
 *      Because it will be initialized whether it is used or not, what memory is not wasted is to initialize when it is used
 *      Therefore, when it is used in large quantities, using hungry man style singleton will waste memory
 */
public class HungrySingleton {
    // static is initialized when the class is loaded
    private static final HungrySingleton hungrySingleton = new HungrySingleton();

    private HungrySingleton(){

    }

    public HungrySingleton getInstance(){
        return hungrySingleton;
    }

}
  • static state
public class HungryStaticSingleton {
    //Class loading order
    //Static before dynamic
    //First up, then down
    //Attribute before method
    private static final HungryStaticSingleton hungryStaticSingleton;

    //It doesn't make any difference. Install B
    static {
        hungryStaticSingleton = new HungryStaticSingleton();
    }

    private HungryStaticSingleton(){

    }

    public HungryStaticSingleton getInstance(){
        return hungryStaticSingleton;
    }

}

3.2 lazy singleton (an instance is created only when it is called externally)

advantage

Advantages: save memory

shortcoming

Disadvantages: threads are unsafe. Two threads may call this instance

case

/**
 * Advantages: save memory
 * Disadvantages: threads are unsafe. Two threads may call this instance
 */
public class LazySimpleSingleton {

    private static LazySimpleSingleton lazySimpleSingleton ;

    // Privatization construction method
    private LazySimpleSingleton(){

    }

    // Provide a global access point
    public synchronized static LazySimpleSingleton getInstance(){
        if (lazySimpleSingleton == null){
            lazySimpleSingleton = new LazySimpleSingleton();
        }
        return lazySimpleSingleton;
    }
}

The authentication thread is unsafe

  • One thread
/**
 * One thread
 */
public class ExectorThread implements Runnable{
    @Override
    public void run() {
        LazySimpleSingleton lazySimpleSingleton = LazySimpleSingleton.getInstance();
        System.out.println(Thread.currentThread().getName() + ":" +lazySimpleSingleton);
    }
}

  • test
public class LazySimpleSingletonTest {
    public static void main(String[] args) {
        Thread t1 = new Thread(new ExectorThread());
        Thread t2 = new Thread(new ExectorThread());
        t1.start();
        t2.start();
        System.out.println("End");
    }
}

Operation results

  • Same instance
    1. Execute in normal sequence
    2. The latter covers the former
  • Different instances
    Enter the conditions at the same time and return in order

Solve thread insecurity - lock (let one thread execute and then another thread execute)

/**
 * Advantages: save memory
 * Disadvantages: threads are unsafe. Two threads may call this instance
 *
 * Solve thread insecurity - > synchronized locking
 * 			  ->New disadvantage - bottleneck, performance affected
 */
public class LazySimpleSingleton {

    private static LazySimpleSingleton lazySimpleSingleton ;

    // Privatization construction method
    private LazySimpleSingleton(){

    }

    // Provide a global access point
    // Lock to solve thread insecurity
    public synchronized static LazySimpleSingleton getInstance(){
        if (lazySimpleSingleton == null){
            lazySimpleSingleton = new LazySimpleSingleton();
        }
        return lazySimpleSingleton;
    }
}

The impact of locking - a new drawback - is the bottleneck and performance is affected

For example, high-speed railway station entrance inspection

Double check lock solves the disadvantage of locking

/**
 * Double lock
 * Advantages: high performance and thread safety
 * Disadvantages: readability is more difficult and not elegant enough
 */
public class LazyDoubleCheckSingleton {

    // Double check lock will cause the problem of instruction reordering. To solve it, add volatile
    private volatile static LazyDoubleCheckSingleton lazyDoubleCheckSingleton;

    private LazyDoubleCheckSingleton(){

    }

    public static LazyDoubleCheckSingleton getInstance(){
        // Check for blockage
        if (lazyDoubleCheckSingleton == null){
            synchronized (LazyDoubleCheckSingleton.class){
                // Check whether you want to recreate the instance
                if (lazyDoubleCheckSingleton == null){
                    lazyDoubleCheckSingleton = new LazyDoubleCheckSingleton();
                }
            }
        }
        
        return lazyDoubleCheckSingleton;
    }
}

Solve the disadvantage of not elegant double check lock (static internal class writing)

/**
 * It looks like a hungry man, but it's actually a lazy man
 * Load - > lazystatic innerclasssingleton Class starts loading
 *        LazyStaticInnerClassSingleton$LazyHolder.class Only return lazyholder INSTANCE; Was loaded
 *
 *  Advantages: elegant writing, using Java's own syntax characteristics, high performance and avoiding memory waste
 *  Disadvantages: can be destroyed by reflection 1
 */
public class LazyStaticInnerClassSingleton {
    
    private LazyStaticInnerClassSingleton(){
        
    }
    
    private static LazyStaticInnerClassSingleton getInstance(){
        return LazyHolder.INSTANCE;
    }

    /**
     * Static inner class
     */
    private static class LazyHolder{
        private static final LazyStaticInnerClassSingleton INSTANCE = new LazyStaticInnerClassSingleton();
    }
}

Restore the scene of reflection destruction singleton

/**
 * Destroyed a single case
 */
public class ReflectTest {
    public static void main(String[] args) {
        try {
            Class<?> clazz = LazyStaticInnerClassSingleton.class;
            Constructor<?> c = clazz.getDeclaredConstructor(null);

            // Because the constructor is private, you can ignore private settings here
            c.setAccessible(true);

            Object instance1 = c.newInstance();
            Object instance2 = c.newInstance();

            System.out.println(instance1);
            System.out.println(instance2);

            System.out.println(instance1 == instance2);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

Scenario of solving single case of reflection destruction

/**
 * It looks like a hungry man, but it's actually a lazy man
 * Load - > lazystatic innerclasssingleton Class starts loading
 *        LazyStaticInnerClassSingleton$LazyHolder.class Only return lazyholder INSTANCE; Was loaded
 *
 *  Advantages: elegant writing, using Java's own syntax characteristics, high performance, avoiding memory waste and can not be destroyed by reflection
 *  Disadvantages: the construction method is not elegant enough
 */
public class LazyStaticInnerClassSingleton {

    private LazyStaticInnerClassSingleton(){
        if (LazyHolder.INSTANCE != null){
            throw new RuntimeException("Illegal access");
        }
    }

    private static LazyStaticInnerClassSingleton getInstance(){
        return LazyHolder.INSTANCE;
    }

    /**
     * Static inner class
     */
    private static class LazyHolder{
        private static final LazyStaticInnerClassSingleton INSTANCE = new LazyStaticInnerClassSingleton();
    }
}

3.3 registered singleton (CACHE each instance in a unified container and obtain the instance with a unique identifier)

Enumerative case code

/**
 * Very elegant
 * But because INSTANCE is declared from the beginning, it is similar to the hungry man style
 * It is not suitable for use in large quantities
 * Next, it's up to spring IOC
 */
public enum  EnumSingleton {

    INSTANCE;

    private Object data;

    public Object getData() {
        return data;
    }

    public void setData(Object data) {
        this.data = data;
    }

    // Provide a global access point
    public static EnumSingleton getInstance(){
        return INSTANCE;
    }

}

Reflection test

/**
*Enumeration cannot create an instance
*/
public class EnumSingletonTest {
    public static void main(String[] args) {
//        EnumSingleton enumSingleton = EnumSingleton.getInstance();
//        enumSingleton.setData(new Object());

        try {
            Class clazz = EnumSingleton.class;
            Constructor c = clazz.getDeclaredConstructor(String.class, int.class);

            // setAccessible is required because it is private
            c.setAccessible(true);
            Object o = c.newInstance();
            System.out.println(o);
        }catch (Exception e){
            e.printStackTrace();
        }

    }
}

result

Container to solve the problem of single case in mass production

Because enumeration is not suitable for mass production, you can use container to solve mass production singletons, using Spring

public class ContainerSingleton {

    private ContainerSingleton(){

    }

    private static Map<String,Object> ioc = new ConcurrentHashMap<String, Object>();

    public static Object getInstance(String className){
        Object instance = null;
        if (!ioc.containsKey(className)){
            try {
                instance = Class.forName(className).newInstance();
                ioc.put(className,instance);
            }catch (Exception e){
                e.printStackTrace();
            }
            return instance;
        }else {
            return ioc.get(className);
        }
    }
}
  • test
public class ContainerSingletonTest {
    public static void main(String[] args) {
        Object instance1 = ContainerSingleton.getInstance("com.DesignLearn.SingletonPattern.test.pojo");
        Object instance2 = ContainerSingleton.getInstance("com.DesignLearn.SingletonPattern.test.pojo");
        System.out.println(instance1);
        System.out.println(instance2);
        System.out.println(instance1 == instance2);
    }
}

Restore the scenario of deserialization breaking singleton

  • serialize
    Converts the state of an object in memory into bytecode
    Write bytecode to disk through IO output stream
    Permanent preservation

  • Deserialization
    Read the persistent bytecode content into memory through IO input stream
    Convert to a Java object

  • Case code

public class SeriableSingleton implements Serializable {


    // serialize
    // Converts the state of an object in memory into bytecode
    // Write bytecode to disk through IO output stream
    // Permanent preservation

    // Deserialization
    // Read the persistent bytecode content into memory through IO input stream
    // Convert to a Java object


    private static  SeriableSingleton seriableSingleton = new SeriableSingleton();

    private SeriableSingleton(){

    }

    public static SeriableSingleton getInstance(){
        return seriableSingleton;
    }
}

  • test
public class SeriableSingletonTest {
    public static void main(String[] args) {
        SeriableSingleton s1 = null;
        SeriableSingleton s2 = SeriableSingleton.getInstance();

        FileOutputStream fos = null;

        try{
            fos = new FileOutputStream("SeriableSingleton.obj");
            ObjectOutputStream oos = new ObjectOutputStream(fos);
            oos.writeObject(s2);
            oos.flush();
            oos.close();

            FileInputStream fis = new FileInputStream("SeriableSingleton.obj");
            ObjectInputStream ois = new ObjectInputStream(fis);
            s1 =(SeriableSingleton) ois.readObject();
            ois.close();

            System.out.println(s1);
            System.out.println(s2);
            System.out.println(s1 == s2);
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}

  • result

    The result should be the same

  • resolvent

public class SeriableSingleton implements Serializable {


    // serialize
    // Converts the state of an object in memory into bytecode
    // Write bytecode to disk through IO output stream
    // Permanent preservation

    // Deserialization
    // Read the persistent bytecode content into memory through IO input stream
    // Convert to a Java object


    private static  SeriableSingleton seriableSingleton = new SeriableSingleton();

    private SeriableSingleton(){

    }

    public static SeriableSingleton getInstance(){
        return seriableSingleton;
    }

    // Solve the serialization problem
    private Object readResolve(){
        return seriableSingleton;
    }
}

3.4 ThreadLocal singleton (ensuring global uniqueness within threads and inherent thread safety)

Case code

  • ThreadLocalSingleton
public class ThreadLocalSingleton {

    private static final ThreadLocal<ThreadLocalSingleton> threadLocalInstance =
            new ThreadLocal<ThreadLocalSingleton>(){
                @Override
                protected ThreadLocalSingleton initialValue() {
                    return new ThreadLocalSingleton();
                }
            };

    private ThreadLocalSingleton(){

    }

    public static ThreadLocalSingleton getInstance(){
        return threadLocalInstance.get();
    }
}
  • Test ThreadLocalSingletonTest
public class ThreadLocalSingletonTest {

    public static void main(String[] args) {
        System.out.println(ThreadLocalSingleton.getInstance());
        System.out.println(ThreadLocalSingleton.getInstance());
        System.out.println(ThreadLocalSingleton.getInstance());

        Thread t1 = new Thread(new ExectorThread());
        Thread t2 = new Thread(new ExectorThread());
        t1.start();
        t2.start();
        System.out.println("End");
    }
}
  • Test execorthread
/**
 * One thread
 */
public class ExectorThread implements Runnable{
    @Override
    public void run() {
        ThreadLocalSingleton threadLocalSingleton = ThreadLocalSingleton.getInstance();
        System.out.println(ThreadLocalSingleton.getInstance());
        System.out.println(Thread.currentThread().getName() + ":" +threadLocalSingleton);
    }
}

4. Summary

Advantages of singleton mode

  • There is only one instance in memory
  • Multiple utilization of resources can be avoided
  • Set global access points to strictly control access

Disadvantages of singleton mode

  • Without interface, it is difficult to expand
  • If you want to extend the singleton object, you can only modify the code, and there is no other way

Key summary

  • Privatization constructor
  • Ensure thread safety
  • Delayed loading
  • Prevent serialization and deserialization from destroying singletons
  • Single case of defensive reflex attack