[Android design pattern application] talk about the singleton pattern in Android

Simple definition

  • For the singleton pattern, you development students are already familiar with it. It is also widely used in various projects. No matter what high-level programming language is used, the design pattern is always accompanied by it.
  • In short, the singleton pattern is to ensure that there is only one instantiated object in a class, and then provide a global access point

Advantages and disadvantages of singleton mode

  1. Main advantages:
  • Provides controlled access to unique instances.
  • Because there is only one object in the system memory, it can save system resources. For some objects that need to be created and destroyed frequently, the singleton mode can undoubtedly improve the performance of the system.
  • Variable instances are allowed.
  1. Main disadvantages:
  • Since there is no abstraction layer in the simple interest pattern, it is very difficult to extend the singleton class.
  • The responsibility of singleton class is too heavy, which violates the "single responsibility principle" to a certain extent.
  • Abusing singleton will bring some negative problems. For example, in order to save resources, designing database connection pool objects as singleton classes may lead to too many programs sharing connection pool objects and connection pool overflow; If the instantiated object is not used for a long time, the system will consider it as garbage and be recycled, which will lead to the loss of object state.

On the implementation of java and kotlin

Several singleton patterns in Kotlin

Hungry Han style single case

  • Hungry Chinese singleton mode is a relatively simple way to implement singleton mode. Its feature is that the instance object will be instantiated whether or not the singleton instance is needed.
  • The implementation in kotlin is very simple. You only need to define an object object expression. There is no need to manually set the constructor privatization and provide global access points. The kotlin compiler has done a good job for you
object KSingleton : Serializable {//Implement the Serializable serialization interface, and control the deserialization through the private and instantiated readResolve method
    fun doSomething() {
        println("do some thing")
    }

    private fun readResolve(): Any {//Prevents singleton objects from regenerating objects during deserialization
        return KSingleton//Since the hook method readResolve will be called during deserialization, you only need to return the current KSingleton object instead of creating a new object
    }
}

Thread safe lazy singleton

  • Create the singleton instance when the class is loaded, and initialize it when we use it
class KLazilySingleton private constructor() : Serializable {
    fun doSomething() {
        println("do some thing")
    }
    companion object {
        private var mInstance: KLazilySingleton? = null
            get() {
                return field ?: KLazilySingleton()
            }

        @JvmStatic
        @Synchronized//Add synchronized lock
        fun getInstance(): KLazilySingleton {
            return requireNotNull(mInstance)
        }
    }
    //Prevents singleton objects from regenerating objects during deserialization
    private fun readResolve(): Any {
        return KLazilySingleton.getInstance()
    }
}

DCL(double check lock) transform lazy single case

  • The thread safe singleton mode directly uses the synchronized synchronization lock to lock the getInstance method. Each time the method is called, the lock must be obtained. However, if the singleton has been initialized, it is not necessary to apply for synchronization lock. Just return the singleton class instance directly. So there is the DCL implementation singleton method

  • Moreover, in kotlin, a single instance of thread safe DCL can be supported. It can be said that it is very, very simple. It is only about 3 lines of code, that is, Companion Object + lazy attribute proxy. Let's take a look

class KLazilyDCLSingleton private constructor() : Serializable {//private constructor() constructor privatization

    fun doSomething() {
        println("do some thing")
    }

    private fun readResolve(): Any {//Prevents singleton objects from regenerating objects during deserialization
        return instance
    }

    companion object {
        //By annotating @JvmStatic, making instance call in Java is just like calling static function.
        //Similar to KLazilyDCLSingleton.getInstance(), if it is not annotated, it must be called in Java as follows: KLazilyDCLSingleton.Companion.getInstance()
        @JvmStatic
        //Use the lazy property proxy and specify the LazyThreadSafetyMode as SYNCHRONIZED mode to ensure thread safety
        val instance: KLazilyDCLSingleton by lazy(LazyThreadSafetyMode.SYNCHRONIZED) { KLazilyDCLSingleton() }
    }
}

Static inner class singleton

  • Although DCL can solve the problems of resource consumption, redundant synchronized synchronization, thread safety and so on to a certain extent, there will be DCL failure in some cases
  • The singleton mode of DCL is generally not recommended in a multithreaded environment. Therefore, the static inner class singleton implementation is introduced
class KOptimizeSingleton private constructor(): Serializable {//private constructor() constructor privatization
    companion object {
        @JvmStatic
        fun getInstance(): KOptimizeSingleton {//Global access point
            return SingletonHolder.mInstance
        }
    }

    fun doSomething() {
        println("do some thing")
    }

    private object SingletonHolder {//Static inner class
        val mInstance: KOptimizeSingleton = KOptimizeSingleton()
    }

    private fun readResolve(): Any {//Prevents singleton objects from regenerating objects during deserialization
        return SingletonHolder.mInstance
    }
}

Enumeration singleton

  • Enumeration singleton implementation is to prevent deserialization, because we all know that enumeration class deserialization will not create new object instances
  • The serialization mechanism of enumeration type ensures that only existing enumeration type instances will be found, rather than creating new instances
enum class KEnumSingleton {
    INSTANCE;

    fun doSomeThing() {
        println("do some thing")
    }
}

Several singleton patterns in Java

Lazy (thread unsafe)

//Lazy singleton class. Instantiate yourself on the first call   
public class Singleton {  
    //Private constructor
    private Singleton() {} 
    //Private static variable 
    private static Singleton single=null;  
    //Exposed public static methods   
    public static Singleton getInstance() {  
         if (single == null) {    
             single = new Singleton();  
         }    
        return single;  
    }  
}
  • Generally speaking, lazy style is divided into three parts: private construction method, private global static variable and public static method
  • The static modifier static keyword plays an important role. We know that in a program, any variable or code is stored by the system automatically allocating memory during compilation. The so-called static means that the memory allocated after compilation will always exist until the program exits the memory. Therefore, it ensures that once an instance of a singleton class is created, It will not be recycled by the system unless it is manually set to null.
  • The disadvantage of this method is that the thread is unsafe. The solution is to use the synchronized keyword, which is the second way to write the singleton mode.

Lazy (thread safe)

public class Singleton {  
    //Private static variable
    private static Singleton instance;  
    //Private construction method
    private Singleton (){};
    //Public synchronous static method
    public static synchronized Singleton getInstance() {  
    if (instance == null) {  
        instance = new Singleton();  
    }  
    return instance;  
    }  
}  
  • The synchronized keyword is added to the getInstance () method of this singleton implementation, which tells Java (JVM) that getInstance is a synchronous method.
  • Synchronization means that when two concurrent threads access the synchronized synchronization method in the same class, only one thread can be executed at a time, and the other thread must wait for the current thread to execute. Therefore, the synchronization method makes the thread safe and ensures that there is only one instance in a single instance.
  • However, its disadvantage is that it synchronizes every time getInstance () is called, resulting in unnecessary synchronization overhead. This mode is generally not recommended.

Hungry Han (thread safe)

//Hungry Chinese singleton class. It has been instantiated by itself during class initialization   
public class Singleton {  
    //Static variables decorated with static exist permanently once they are created in memory
    private static Singleton instance = new Singleton();  
    private Singleton (){}  
    public static Singleton getInstance() {  
    return instance;  
    }  
} 
  • Hungry Chinese style has created a static object for the system to use at the same time as the class is created, which will not change in the future, so it is inherently thread safe. Where instance=new Singleton() can be written as:
 static {  
    instance = new Singleton();  
    }  
  • The hungry man singleton mode, which belongs to a variant, is also based on the classloder mechanism to avoid the synchronization problem of multiple threads. instance is instantiated when the class is loaded.

DCL double check mode

public class Singleton {  
    private static Singleton singleton;  //Static variable
    private Singleton (){}  //private constructors 
    public static Singleton getInstance() {  
      if (singleton == null) {  //First layer verification
          synchronized (Singleton.class) {  
          if (singleton == null) {  //Second layer verification
              singleton = new Singleton();  
          }  
        }  
      }  
    return singleton;  
    }  
}  
  • The special feature of this pattern lies in the getInstance () method, in which the singleton is judged twice whether it is empty. The first layer judges to avoid unnecessary synchronization, and the second layer judges to create an instance when it is null.

Let's analyze specifically: suppose thread A executes singleton = new Singleton(); Statement. It looks like A code here, but it is not an atomic operation. This code will eventually be compiled into multiple assembly instructions. It will do roughly three things:

  1. Allocate memory to Singleton instances
  2. Call the constructor of Singleton() to initialize the member field
  3. Point the singleton object to the allocated memory space (that is, the singleton is not empty)

However, after JDK 1.5, the official gave the volatile keyword and the code defined by singleton in order to solve the problem of DCL failure.

private volatile static Singleton singleton;  //Use volatile keyword

Static inner class singleton mode

public class Singleton {  
    private Singleton (){} ;//Private constructor
    public static final Singleton getInstance() {  
        return SingletonHolder.INSTANCE;  
    }  
    //Defined static inner class
    private static class SingletonHolder {  
        private static final Singleton INSTANCE = new Singleton();  //Where the instance is created
    }  
}  
  • The INSTANCE will not be initialized when the Singleton class is loaded for the first time. The INSTANCE will be initialized only when the getInstance () method of Singleton is called for the first time.
  • Therefore, calling getInstance () for the first time will cause the virtual machine to load the SingletonHolder class. This method can not only ensure the uniqueness of the singleton object, but also delay the instantiation of the singleton.

Enumeration singleton
The previous implementation methods of singleton mode are generally a little troublesome, or some situations occur in some specific cases. The following describes the implementation of enumeration singleton mode:

public enum Singleton {  //enum enumeration class
    INSTANCE;  
    public void whateverMethod() {  

    }  
}

The biggest advantage of enumeration singleton mode is that it is easy to write. Enumeration is the same as ordinary classes in java. It can not only have fields, but also have its own methods. The most important thing is that the default enumeration instance is thread safe, and it is a singleton in any case. Even in the process of deserialization, the enumeration singleton will not regenerate a new instance. For other methods, the following methods must be added:

private Object readResolve()  throws ObjectStreamException{
    return INSTANCE;
}

In this way, we can ensure that no new methods will be generated during deserialization

Using containers to implement singleton mode

public class SingletonManager { 
  private static Map<String, Object> objMap = new HashMap<String,Object>();//Use HashMap as cache container
  private Singleton() { 
  }
  public static void registerService(String key, Objectinstance) {
    if (!objMap.containsKey(key) ) {
      objMap.put(key, instance) ;//The first time is to save to Map
    }
  }
  public static ObjectgetService(String key) {
    return objMap.get(key) ;//Returns the object corresponding to the key
  }
}
  • At the beginning of the program, a variety of singleton patterns are injected into a unified management class, and the corresponding types of objects are obtained according to the key.

Scenario application

1. When should we consider using singleton mode?

  • The system only needs one instance object. For example, the system requires a unique * serial number generator or resource manager, or only one object is allowed to be created due to excessive resource consumption.
  • A single instance of a client calling class is allowed to use only one public access point. In addition to the public access point, the instance cannot be accessed through other ways.

2. Let's analyze some source codes in Android

EventBus framework commonly used in Android

  • We can take a look at how to use singleton mode in EventBus, mainly using double check DCL
static volatile EventBus defaultInstance;
    public static EventBus getDefault() {
        if (defaultInstance == null) {
            synchronized (EventBus.class) {
                if (defaultInstance == null) {
                    defaultInstance = new EventBus();
                }
            }
        }
        return defaultInstance;
    }

In this way, its resource utilization will be very high, and the singleton object will be instantiated only when it is executed for the first time, but it will be slightly slower when it is loaded for the first time, which can be accepted

Singleton mode implementation of LayouInflater

  • Basic Usage
LayoutInflater mInflater = LayoutInflater.from(this);

It is estimated that no one will be unfamiliar with the above writing method. Get the example of layoutinflator. Let's take a look at the specific source code implementation:

1. Obtain the LayoutInflater service through LayoutInflater.from(context)

/**
 * Obtains the LayoutInflater from the given context.
 */
public static LayoutInflater from(Context context) {
    LayoutInflater LayoutInflater =
            (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    if (LayoutInflater == null) {
        throw new AssertionError("LayoutInflater not found.");
    }
    return LayoutInflater;
}

2. See how context.getSystemService works. The implementation class of context is ContextImpl class. Click in to have a look

@Override
public Object getSystemService(String name) {
    return SystemServiceRegistry.getSystemService(this, name);
}

3. Enter the SystemServiceRegistry class

/**
 * Gets a system service from a given context.
 */
public static Object getSystemService(ContextImpl ctx, String name) {
    ServiceFetcher<?> fetcher = SYSTEM_SERVICE_FETCHERS.get(name);
    return fetcher != null ? fetcher.getService(ctx) : null;
}
  • So far, I'm sure you've already felt that this is the case of using containers to implement the singleton mode mentioned above. By comparison, it's true
private static final HashMap<String, ServiceFetcher<?>> SYSTEM_SERVICE_FETCHERS =
        new HashMap<String, ServiceFetcher<?>>();
  • Use map to save system services through key value pairs. Inject when calling registerService.
/**
 * Statically registers a system service with the context.
 * This method must be called during static initialization only.
 */
private static <T> void registerService(String serviceName, Class<T> serviceClass,
        ServiceFetcher<T> serviceFetcher) {
    SYSTEM_SERVICE_NAMES.put(serviceClass, serviceName);
    SYSTEM_SERVICE_FETCHERS.put(serviceName, serviceFetcher);
}

4. We can see when these services are registered

static {

registerService(Context.LAYOUT_INFLATER_SERVICE, LayoutInflater.class,
        new CachedServiceFetcher<LayoutInflater>() {
    @Override
    public LayoutInflater createService(ContextImpl ctx) {
        return new PhoneLayoutInflater(ctx.getOuterContext());
    }});
}
  • Obviously, this is a static code block to register the service. It is executed when the class is loaded for the first time, and only once to ensure the uniqueness of the instance
  • It can be seen from this process that the system stores the service in the HashMap in the form of key value pairs. When users use it, they only need to obtain the specific service object. For the first time, they call getSystemService to obtain the specific object. For the first time, they call registerService to cache the object in a list through the map, You can just take it out of the container when you reuse it next time. Avoid creating objects repeatedly, so as to achieve the effect of singleton. Reduced resource consumption.
  • In the Android source code, when the APP is started, when the virtual machine loads the class for the first time, it will register various servicefetchers, such as layoutinflator service. These services are stored in a HashMap in the form of key value pairs. When using, users only need to obtain the corresponding ServiceFetcher according to the key, and then obtain the specific service object through the getService function of the ServiceFetcher object. When getting the service object for the first time, the createservice function of ServiceFetcher will be called to create the service object, and then the object will be cached in a list. When getting the object again next time, it will be obtained directly from the cache to avoid creating the object repeatedly, so as to achieve the effect of singleton. The system core services in Android exist in the form of a single instance, reducing resource consumption.

ImageLoader picture loading framework

  • The instance creation of the image loading framework ImageLoader uses the singleton mode, because the ImageLoader contains thread pool, cache system and network requests, which consumes resources and should not create multiple objects. The singleton mode is needed at this time
ImageLoader.getInstance();// Create a global instance in your own Application
.....
//Source code of getInstance() execution
 public static ImageLoader getInstance() {
        if(instance == null) {//Double check DCL singleton mode
            Class var0 = ImageLoader.class;
            synchronized(ImageLoader.class) {//Synchronous code block
                if(instance == null) {
                    instance = new ImageLoader();//Create a new instance
                }
            }
        }
        return instance;//Returns an instance
    }

Therefore, if we need to consume too much resources to create an object, we can consider the singleton mode

epilogue

The above are the analysis and summary of the single case mode sorted out by my classmates in combination with the online data. In short, the single case mode is very common in the design mode. It is simple but simple at the same time, which is worthy of further discussion. Below, I briefly summarize a single case mode

  • A core principle is private construction and obtaining an instance through static methods.
  • In this process, thread safety must be guaranteed.
  • It is recommended to use static internal implementation singletons or double check singletons with Volatile keyword

If you want to know more about Android, you can click the small card below to view it, which records many Android knowledge points. Finally, please praise and support!!!

Tags: Java Android Design Pattern

Posted on Tue, 28 Sep 2021 14:56:26 -0400 by davidjmorin