[Singleton Pattern] Usage Comparison of Java Design Patterns

https://blog.csdn.net/luofen521/article/details/51788230

1. Definition

Make sure that there is only one instance of a class, that it can instantiate itself and make that instance available to the entire system.

2. Application scenarios

  1. When multiple objects are generated, it will consume too much resources, such as IO and data operations
  2. There should only be one and only one object of a certain type, such as Application in Android.

3. Consider the situation

  1. Instances are not unique due to multithreading.
  2. The deserialization process produces a new instance.

4. Implementation

4.1 Ordinary singleton pattern

/**
 * 普通模式
 * @author josan_tang
 */
public class SimpleSingleton {
    //1.static单例变量
    private static SimpleSingleton instance;

    //2.私有的构造方法
    private SimpleSingleton() {

    }

    //3.静态方法为调用者提供单例对象
    public static SimpleSingleton getInstance() {
        if (instance == null) {
            instance = new SimpleSingleton();
        }
        return instance;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

In the case of multi-threading and high concurrency, this writing will have obvious problems. When thread A calls the getInstance method and executes to line 16, it detects that the instance is null, so it executes line 17 to instantiate the instance, and when line 17 is not executed When it is finished, thread B calls the getInstance method again. At this time, it detects that the instance is still empty, so thread B also executes line 17 to create a new instance. At this time, the instance obtained by thread A and thread B is not the same, which violates the definition of singleton.

4.2 Hungry singleton pattern

/**
 * 饿汉单例模式
 * @author josan_tang
 */
public class EHanSingleton {
    //static final单例对象,类加载的时候就初始化
    private static final EHanSingleton instance = new EHanSingleton();

    //私有构造方法,使得外界不能直接new
    private EHanSingleton() {
    }

    //公有静态方法,对外提供获取单例接口
    public static EHanSingleton getInstance() {
        return instance;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

Hungry singleton mode solves the problem of multi-threaded concurrency, because instance is instantiated when this class is loaded. When the getInstatnce method is called, the object is always initialized when the class is loaded (except in the case of deserialization). But this also brings another problem. If a large number of classes adopt the hungry singleton mode, then in the class loading stage, many objects that have not been used for the time being will be initialized, which will definitely waste memory and affect performance. , we still have to tend to the 4.1 approach, and only initialize the instance when the getInstance method is called for the first time. Please continue to see 4.3 usage.

4.3 The Lazy Singleton Pattern

import java.io.Serializable;

/**
 * 懒汉模式
 * @author josan_tang
 */
public class LanHanSingleton {
    private static LanHanSingleton instance;

    private LanHanSingleton() {

    }

    /**
     * 增加synchronized关键字,该方法为同步方法,保证多线程单例对象唯一
     */
    public static synchronized LanHanSingleton getInstance() {
        if (instance == null) {
            instance = new LanHanSingleton();
        }
        return instance;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

The only difference from 4.1 is that the synchronized keyword is added before the getInstance method, making the getInstance method a synchronized method, which ensures that when getInstance is called for the first time, that is, when the instance is instantiated, other calls will not enter the method. , which ensures the uniqueness of singleton objects in multithreading.

Advantages: The singleton object is instantiated at the first call, effectively saving memory and ensuring thread safety.

Disadvantage: Synchronization is for the method, and every time you call getInstance in the future (even if the instance has been instantiated), it will also be synchronized, causing unnecessary synchronization overhead. This method is not recommended.

4.4 Double CheckLock (DCL) singleton mode

/**
 * Double CheckLock(DCL)模式
 * @author josan_tang
 *
 */
public class DCLSingleton {
    //增加volatile关键字,确保实例化instance时,编译成汇编指令的执行顺序
    private volatile static DCLSingleton instance;

    private DCLSingleton() {

    }

    public static DCLSingleton getInstance() {
        if (instance == null) {
            synchronized (DCLSingleton.class) {
                //当第一次调用getInstance方法时,即instance为空时,同步操作,保证多线程实例唯一
                //当以后调用getInstance方法时,即instance不为空时,不进入同步代码块,减少了不必要的同步开销
                if (instance == null) {
                    instance = new DCLSingleton();
                }
            }
        }
        return instance;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27

DCL invalidation:

Before JDK1.5, there may be problems with DCL implementation. Although the 20 lines in the above code are a sentence of code in Java, it is not a real atomic operation.

instance = new DCLSingleton();
  • 1

It is compiled into the final assembly instructions, which will have the following 3 stages:

  1. Allocate memory to DCLSingleton instance
  2. Call the constructor of DCLSingleton to initialize member variables.
  3. Point the instance to the allocated memory space (after this operation, the instance is not null)

Before jdk1.5, the above steps 2 and 3 cannot guarantee the order, that is to say, it may be 1-2-3 or 1-3-2. If it is 1-3-2, when thread A finishes executing step 3 (instance is not null), but has not finished executing step 2, thread B calls the getInstance method again. At this time, what thread B gets is that thread A has not executed The instance of step 2 (the constructor has not been executed), when thread B uses such an instance, there may be an error. This is DCL failure.

After jdk1.5, the volatile keyword can be used to ensure the execution order of assembly instructions, although it will affect the performance, but compared with the correctness of the program, it can be ignored.

Java memory model

Advantages: Instance is instantiated when getInstance is executed for the first time, which saves memory; in the case of multi-threading, it is basically safe; and after instance is instantiated, when getInstance is called again, there will be no synchronization consumption.

Disadvantages: below jdk1.5, DCL may fail; Java memory model affects the failure; after jdk1.5, using the volatile keyword can solve the problem of DCL failure, but it will affect some performance.

4.5 Static inner class singleton pattern

/**
 * 静态内部类实现单例模式
 * @author josan_tang
 *
 */
public class StaticClassSingleton {
    //私有的构造方法,防止new
    private StaticClassSingleton() {

    }

    public static StaticClassSingleton getInstance() {
        return StaticClassSingletonHolder.instance;
    }

    /**
     * 静态内部类
     */
    private static class StaticClassSingletonHolder {
        //第一次加载内部类的时候,实例化单例对象
        private static final StaticClassSingleton instance = new StaticClassSingleton();
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

When the StaticClassSingleton class is loaded for the first time, the instance will not be instantiated. Only when the getInstance method is called for the first time, the Java virtual machine will load the StaticClassSingletonHolder class and then instantiate the instance. This delays instantiating the instance and saves memory. And also thread safe. This is a recommended singleton pattern.

4.6 Enumeration Singleton Pattern

/**
 * 枚举单例模式
 * @author josan_tang
 *
 */
public enum EnumSingleton {
    //枚举实例的创建是线程安全的,任何情况下都是单例(包括反序列化)
    INSTANCE;

    public void doSomething(){

    } 
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

Enumerations not only have fields but also have their own methods, and the creation of enumeration instances is thread-safe, even when deserializing, no new instances are created. In addition to the enumeration mode, other implementations create new objects when deserializing.

In order to prevent the object from creating a new object when deserializing, the following method needs to be added:

    private Object readResole() throws ObjectStreamException {
        return instance;
    }
  • 1
  • 2
  • 3

This is a hook function, which is called when the object is created by deserialization. We return the instance directly. That is, instead of creating a new object by default, return the instance directly.

4.7 Container Singleton Pattern

import java.util.HashMap;
import java.util.Map;

/**
 * 容器单例模式
 * @author josan_tang
 */
public class ContainerSingleton {
    private static Map<String, Object> singletonMap = new HashMap<String, Object>();

    //单例对象加入到集合,key要保证唯一性
    public static void putSingleton(String key, Object singleton){
        if (key != null && !"".equals(key) && singleton != null) {
            if (!singletonMap.containsKey(key)) {   //这样防止集合里有一个类的两个不同对象
                singletonMap.put(key, singleton);   
            }
        }
    }

    //根据key从集合中得到单例对象
    public static Object getSingleton(String key) {
        return singletonMap.get(key);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25

When the program is initialized, a variety of singleton type objects are added to a singleton collection for unified management. When in use, get the singleton object from the collection by key. This method is more common in singletons in the system. For example, the system-level service in Android is a singleton pattern in the form of a collection. For example, the commonly used LayoutInfalter, we generally use the following code in the getView method in Fragment:

View view = LayoutInflater.from(context).inflate(R.layout.xxx, null);
  • 1

In fact, LayoutInflater.from(context) is to get an instance of LayoutInflater, see the following Android source code:

    /**
     * Obtains the LayoutInflater from the given context.
     */
    public static LayoutInflater from(Context context) {
        //通过key,得到LayoutInflater实例
        LayoutInflater LayoutInflater =
                (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        if (LayoutInflater == null) {
            throw new AssertionError("LayoutInflater not found.");
        }
        return LayoutInflater;
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

Summarize

No matter what form the singleton pattern is implemented in, the core idea is to privatize the constructor and obtain a unique instance through a static method. In the process of obtaining, it is necessary to ensure thread safety and prevent deserialization from regenerating instance objects. Which implementation method to choose depends on the situation, such as high concurrency, JDK version, resource consumption of singleton objects, etc.

name advantage shortcoming Remark
simple mode Simple to implement thread unsafe  
hungry man mode thread safety Memory consumption is too high  
lazy mode thread safety The synchronization method consumes a lot of money  
DCL mode Thread-safe, saves memory Limited jdk version and high concurrency will cause DCL to fail Recommended Use
static inner class pattern Thread safe, save memory It is more difficult to implement Recommended Use
enumeration mode Thread safety, support deserialization Personally feel weird  
Collection mode Unified management and resource saving thread unsafe

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=326023771&siteId=291194637