SoFunction
Updated on 2025-03-11

Android design pattern singleton pattern example

1. Concept

Singleton pattern is one of the most widely used design patterns. When applying this pattern, the class of singleton pattern must ensure that only one instance exists. It only requires one instance for the entire program, which is usually very resource-consuming, such as thread pool, cache, network request, IO operations, access to databases, etc. Since the class consumes more resources, there is no need to let it construct multiple instances. This is a better use scenario for singleton mode.

1.1 Singleton Class

Singleton Pattern: A class has and only one instance, and is instantiated to the entire system, also known as a singleton class.

There are three key points in singleton mode:

1. A certain class can only have one instance.

2. This instance must be created by yourself.

3. This instance must be provided to all other objects.

The specific implementation point is the following:

1. Classes with singleton pattern only provide private constructors.

2. Return a singleton class object through a static method or enumeration.

3. Ensure that the singleton class has and only one static private object, especially in a multi-threaded environment.

4. Provides a static public function to create or obtain its own static private object.

5. Ensure that the singleton class object does not rebuild the object when deserialized.

The implementation of the singleton class generates only one instance, and it provides a static getInstance() factory method, so that customers can access its unique instance; in order to prevent instantiation of it externally, its constructor is designed to be private; inside the singleton class, a static object of type Singleton is defined as a unique instance of external sharing.

1.2 Pros and cons

1.2.1 Advantages

1. Singleton mode provides controlled access to unique instances. Because the singleton class encapsulates its only instance, it can strictly control how and when the customer accesses it.

2. Since there is only one object in the system memory, system resources can be saved. For some object singleton patterns that require frequent creation and destruction, it can undoubtedly improve the performance of the system.

3. Allow variable number of instances. Based on the singleton mode, we can expand and use a method similar to singleton control to obtain a specified number of object instances, which not only saves system resources, but also solves the problem of too many singleton objects sharing lossy performance.

1.2.2 Disadvantages

1. Since there is no abstraction layer in the singleton pattern, it is very difficult to expand the singleton class.

2. The responsibilities of singletons are too heavy, which to a certain extent violates the "principle of single responsibility". Because singleton classes not only act as factory roles, provide factory methods, but also serve as product roles, including some business methods, integrating product creation and product itself functions.

3. Nowadays, many object-oriented languages ​​(such as Java and C#) runtime environments provide automatic garbage collection technology. Therefore, if the instantiated shared object is not used for a long time, the system will consider it to be garbage, and will automatically destroy and recycle resources. It will be re-instified next time it is used, which will lead to the loss of the shared singleton object state.

2. Methods to create singleton pattern

2.1 Hungry Man Style

If you emphasize hunger, you will be more anxious when creating an object instance. If you are hungry, you will create an object instance when loading the class.

This method is very simple, because the instance of a singleton is declared as static and final variables and is initialized when the class is loaded into memory for the first time, so creating an instance itself is thread-safe.

public class SingletonHungry {
    //Initialize when the class is loaded    private static final SingletonHungry singleton = new SingletonHungry();
    private SingletonHungry(){}
    public static SingletonHungry getInstance(){
        return singleton;
    }
}

The disadvantage is that it is not a lazy loading mode, even if the client does not call the getInstance() method, the singleton will be initialized from the beginning after the class is loaded.

The creation method of Hungry Man style will not be used in some scenarios: for example, the creation of a Singleton instance depends on parameters or configuration files, and a certain method must be called to set parameters to it before getInstance(), so this singleton writing method will not be used.

2.2 Lazy

Emphasizing laziness, then don’t worry when creating object instances, when to use and when to create them. Therefore, no object instance is created when loading the object.

2.2.1 Lazy (non-thread safe)

public class SingletonLazy {
    private static SingletonLazy singletonLazy;
    private SingletonLazy(){}
    public static SingletonLazy getInstance(){
        if (singletonLazy == null) {
            singletonLazy = new SingletonLazy();
        }
        return singletonLazy;
    }
}

This generation uses lazy loading mode, but it has fatal problems. When multiple threads call getInstance() in parallel, multiple instances will be created. That is to say, it cannot work properly under multi-threading. So how to solve it? The easiest way is to add a synchronized lock to the getInstance() method.

2.2.2 Lazy (thread safety)

public class SingletonLazy {
    private static SingletonLazy singletonLazy;
    private SingletonLazy(){}
    public static synchronized SingletonLazy getInstance(){
        if (singletonLazy == null) {
            singletonLazy = new SingletonLazy();
        }
        return singletonLazy;
    }
}

The above is to add the synchronized keyword, so getInstance() is a synchronization method to ensure the uniqueness of singleton objects in multi-threaded situations.

Although it is thread-safe and solves the problem of multiple instances, it is not efficient. Because at any time there can only be one thread calling the getInstance() method. However, the synchronization operation only needs to be required on the first call, that is, when the singleton instance object is created for the first time. This leads to a double check lock.

2.3 Double inspection lock

Double checked locking pattern is a method of locking using synchronous blocks. Programmers call it a double check lock, which is also a way to use it frequently online.

Why is it called a double check lock? Because there will be two checks instance == null, once outside the synchronization block, and once inside the synchronization block. Why do we need to check again in the synchronization block? Because multiple threads may enter if outside the synchronization block together, avoid unnecessary synchronization. If no secondary inspection is performed in the synchronization block, multiple instances will be generated to avoid generating multiple instances.

public class SingletonDCL {
    private static SingletonDCL singleton;
    private SingletonDCL(){}
    public static SingletonDCL getInstance(){
        if (singleton == null) {
            synchronized (){
                if (singleton == null) {
                    singleton = new SingletonDCL();
                }
            }
        }
        return singleton;
    }
}

This code looks perfect with double checks, but unfortunately, it's a problem. It mainly depends on singleton = new SingletonDCL(). In fact, this sentence in JVM does roughly the following 3 things:

1. Allocate memory to singleton.

2. Call the SingletonDCL constructor to initialize member variables.

3. Point the singleton object to the allocated memory space (the singleton is not null after executing this step).

However, there is optimization for instruction reordering in the JVM instant compiler. In other words, the order of steps 2 and 3 above cannot be guaranteed, and the final execution order may be 1-2-3 or 1-3-2. If it is the latter, it will be preempted by thread 2 before the execution of 3 is completed but the execution of 2 is not executed. At this time, singleton is already null (but not initialized), so thread 2 will directly return singleton (not executed in step 2), then use it, and then naturally report an error.

We just need to declare the singleton variable as volatile.

public class SingletonDCL {
    private volatile static SingletonDCL singleton;//The variable is declared as volatile    private SingletonDCL(){}
    public static SingletonDCL getInstance(){
        if (singleton == null) {
            synchronized (){
                if (singleton == null) {
                    singleton = new SingletonDCL();
                }
            }
        }
        return singleton;
    }
}

The main reason for using volatile is that it has a feature: prohibiting instruction reordering optimization. That is to say, there is a memory barrier (on the generated assembly code) after the assignment operation of the volatile variable, and the read operation will not be reordered before the memory barrier. For example, in the above example, the fetching operation must be performed after 1-2-3 or after 1-3-2, and there is no case where the value is executed to 1-3 and then the value is obtained.

Of course, there is another rule for the volatile variable: the write operation to a variable occurs first and the read operation facing the variable (the "after" here is the order of time).

2.4 Static inner class

public class SingletonNested {
    // Static internal class    private static class SingletonHolder{
        private static final SingletonNested singleton = new SingletonNested();
    }
    private SingletonNested(){}
    public static SingletonNested getInstance(){
        return ;
    }
}

Using the JVM itself mechanism ensures thread safety issues. Since the static singleton object is not instantiated directly as a member variable of Singleton, SingletonNested will not be instantiated when the class is loaded. The inner class SingletonHolder will be loaded when the getInstance() is called for the first time. A static variable singleton is defined in the inner class. At this time, this member variable will be initialized first. The Java virtual machine will ensure its thread safety and ensure that the member variable can only be initialized once. Since the getInstance() method does not have any thread locks, its performance will not have any impact. s

Since SingletonHolder is private, there is no way to access it except getInstance(), it is lazy and will not synchronize when reading instances, have no performance defects, and do not rely on the JDK version.

2.5 Enumeration

public enum SingletonEnum {
    SINGLETON;
    public void doSomeThing() {
    }
}

We can access the instance through, which is much simpler than calling the getInstance() method. Creating enumerations is thread-safe by default, so you don't need to worry about double checked locking, and it also prevents deserialization from causing recreation of new objects.

summary

The singleton model is implemented in which way, the core idea is the same:

1. The constructor is privatized, and a unique instance is obtained through a static method.

2. Thread safety

Use scenarios:

1. Objects that need to be created and destroyed frequently.

2. Objects that take too much time or too much resources when creating objects, but are often used.

3. Tool-like objects.

4. Frequent access to objects of database or file.

Generally speaking, it is better to use the Hungry style directly. Of course, it is recommended to use the DCL method and static internal class method in the article to create a singleton pattern. If it involves deserializing the creation of objects, try to use enumeration to implement singletons. Of course, the advantage of enumeration singletons is that they are simple, but most application developments rarely use enumerations, and their readability is not very high, so it is not recommended to use them.

III. Expand

3.1 Prevent deserialization

The use of enumerations above prevents deserialization from causing recreation of new objects. So how do you prevent deserialization from causing recreation of new objects in several other ways to implement singleton patterns? That is deserialization. You can refer to the Serialization article.

The deserialization operation provides a very special hook function, with a private readResolve() function in the class, which allows developers to control the deserialization of objects.

public class SingletonDCL implements Serializable {
    private volatile static SingletonDCL singleton;//The variable is declared as volatile    ...
    private Object readResolve() throws ObjectStreamException {
        return singleton;
    }
}

Return a singleton object in the readResolve method instead of regenerating a new object.

3.2 volatile keywords

The Java memory model specifies that all variables are stored in main memory. Each thread also has its own working memory, and the thread's working memory stores the variables used by the thread (these variables are copied from the main memory). All operations (read, assignment) of a thread on a variable must be performed in working memory. Different threads cannot directly access the variables in the other party's working memory, and the transfer of inter-thread variable values ​​needs to be completed through main memory.

effect

1. Thread visibility

When a shared variable is modified by volatile, it ensures that the modified value will be updated to main memory immediately, and when other threads need to read it, it will read the new value in memory.

2. Instruction reordering

Before adding, the instructions were executed concurrently, and the first thread was executed halfway through the other thread. After adding the volatile keyword, different threads are executed step by step in order. For example, the double inspection lock in 2.3 above.

This is the article about the singleton model example of Android design mode. For more related content on Android singleton model, please search for my previous articles or continue browsing the related articles below. I hope everyone will support me in the future!