preface

“I don’t need to tell you how important design patterns are if you want to be a good Android engineer.” “Android Advanced Light” By Liu Wangshu

Okay, okay, I know that design patterns are important, but I can’t do it!

But which design pattern do I start with? Singletons. Who can’t singletons? Hey hey!

Then I opened the book with a layer of dirt:

“Android advanced light” “Android source code design mode analysis and combat” “HeadFirst design mode” “Big word design mode”…… (Poor shooting skills, many gun thieves)

Singletons are implemented in hunger-hungry mode, thread-unsafe lazy mode, thread-safe lazy mode, DCL, static inner classes, enumerations, containers.

What the fuck! 7 ways to write a little singleton? !

And “DCL failure”, “deserialization of serializable singletons”?

No, I’m not a good shot. Get more guns. Get him!

Gun training starts with knowing guns

Let’s start with the definition:

The singleton pattern is used to ensure that a class has only one instance, instantiates that instance itself, and provides a global access point to that instance.

Post a UML class diagram of a single example pattern, and there is really only one class.

Let the pose

1. Hungry

First paste the code:

public class Singleton { private static Singleton instance = new Singleton(); Class private Singleton() {} public static Singleton getInstance() {return instance; }}Copy the code

This is known as the hungrier mode.

In this mode, initialization is done when the class is loaded.

Based on this, we can conclude that the singleton pattern of Hanhan-hungry mode has the following characteristics:

  • 1. Class-based loading mechanism avoids the synchronization problem of multiple threads.

  • 2. Initialization during class loading, resulting in slow class loading speed, but fast object acquisition speed;

  • Lazy loading is not implemented. If this instance is not used from start to finish, it will cause a waste of memory.

The subsequent writing of various singleton modes is based on the development of hunger-man mode. If we understand the above characteristics of hunger-man mode, the understanding of subsequent writing methods will come naturally.

I made a mistake about the timing of class loading and have corrected it.

Classes are loaded when they are first used, not when the Java Virtual machine initializes the application.

Thanks to “orchid heart dance”, he specially to the public number to contact me to correct my mistake.

2. Lazy mode (thread unsafe)

First paste the code:

public class Singleton { private static Singleton instance; // Private construct, Class private Singleton() {} public static Singleton getInstance() {if (instance == null) { instance = new Singleton(); } return instance; }}Copy the code

As you can easily see, the biggest improvement in lazy mode compared to hungry mode is lazy loading.

That is, classes are not loaded when they are not used, and are instantiated the first time they are called. So, it saves memory resources.

In addition, it is important to note that in the case of multithreading, there are two possible problems with this method:

  • 1. Creating multiple instances defeats the purpose of the singleton pattern;

  • 2. The security problem of multithreading may lead to serious consequences. We will talk about “DCL failure problem” later in this article.

As we all know, mobile applications are less likely to have high concurrency, so this singleton pattern actually works, but it’s really not as good as subsequent ones.

3. Lazy mode (thread-safe)

Since the biggest problem with the lazy mode of the previous version is that threads are unsafe, let’s solve this problem.

public class Singleton { private static Singleton instance; // Private construct, Class private Singleton() {} public static synchronized Singleton getInstance() {if (instance) == null) { instance = new Singleton(); } return instance; }}Copy the code

Simply add synchronized to the getInstance() method.

The truth is, however, that this is the least recommended. This is because synchronization is required every time the getInstance() method is called in a multi-threaded environment or not, incurring unnecessary synchronization overhead.

4. Double Check Mode (DCL)

This is pretty good, but there are some problems, so let’s look at the code.

public class Singleton { private static Singleton instance; // Private construct, Class private Singleton() {} public static Singleton getInstance() {// 10 line if (instance == Null) {// 11 line synchronized (Singleton) {// 12 line if (instance == null) {// 13 line instance = new Singleton(); } } } return instance; }}Copy the code

Let’s look at lines 12 to 14 of this code, naturally for lazy loading;

11 lines of code are designed to solve the problem of lazy loading threads being unsafe.

The highlight is in line 10 of the code, which nullates before synchronization and does not synchronize if the class is already instantiated. This solves the problem of unnecessary synchronization overhead caused by synchronization every time the getInstance() method is called.

To sum up, the writing method of DCL has solved all the problems of the above three singletons.

So what’s the problem with writing DCL?

DCL failure problem (underline)!

Instance = new Singleton() = new Singleton() = new Singleton()

  • Allocate memory space to Singleton instance;

  • 2. Call the Singleton constructor to initialize its member variables;

  • 3. Assign instance to the allocated space (in which case instance is not null).

Because the Java compiler allows the processor to execute out of order (cause 1), and before JDK 1.4 and 1.4, the JMM (Java Memory Model) stipulated the write order of Cache and register to main Memory (Cause 2), so the sequence of steps 2 and 3 above cannot be guaranteed. That is, the order of execution could be 1-2-3, or 1-3-2.

There is nothing wrong with 1-2-3. Let’s focus on 1-3-2.

Assume that thread A is instantiating the Singleton, performing steps 1 and 3, but not 2. The constructor of the Singleton has not been executed yet, and the member variable has not been initialized, so the reference to the member variable is actually null.

This is the problem of DCL failure, and it is a serious consequence of the “thread-unsafe slacker pattern” mentioned earlier.

So how do you solve the DCL failure problem?

Prior to JDK 1.4 and 1.4, there was no solution. Fortunately, there are few JDK versions that are this low anymore.

In JDK 1.5 and later, we could declare the instance property with the keyword volatile at line 3 and ensure that the instance object is read from main memory every time.

  private static volatile Singleton instance;
Copy the code

Let’s explain it briefly:

In JMM, there is a distinction between main memory and working memory, where a declared variable exists in main memory and a copy of it exists in working memory. When working memory changes the copy of a variable, it is synchronized from working memory to main memory.

However, when the variable is operated in a non-atomic manner, the synchronization of the variable from the working memory to the main memory is also a non-atomic operation. In the case of multi-threading, if the storage and retrieval happen at the same time, there will be the problem of thread insecurity.

The volatile keyword, on the other hand, is “ordered” and “visible.”

  • order

    Forbid instruction reordering to solve the first cause of DCL failure;

  • visibility

    When a working memory modifies the copy of variables, it will be synchronized to the main memory immediately. Meanwhile, all copies of variables in the working memory will be set to invalid state. Then, when other working memory wants to access this variable, it will be synchronized from the main memory to solve the second cause of DCL failure.

Thus, the volatile keyword resolves DCL invalidation in JDK >= 1.5.

We know that synchronized is expensive. Volatile is less expensive than synchronized, but it still has a performance cost. Is there a better way to write a singleton?

Yes, read on!

5. Implement singletons using static inner classes

That’s a pretty good way to write it, so let’s look at the code.

Public class Singleton{private Singleton(){} public static Singleton getInstance(){// 6 line return SingletonHelper.instance; Private static class SingletonHelper{private final static instance = new Singleton(); }}Copy the code

To understand this pattern, you first need to understand the hungry man pattern. Let’s understand it.

If you look at lines 9 to 11, the instance property in the static inner class SingletonHelper is loaded as the SingletonHelper is loaded, that is, once the static inner class SingletonHelper is loaded, The Singleton class is instantiated;

So, when does the static inner class SingletonHelper load?

Unlike static properties, static inner classes are not loaded with the host class (Singleton) and are loaded by the Java virtual machine the first time a static inner class is called (line 6), thus avoiding multithreaded synchronization issues.

At the same time, this approach enables lazy loading; if the getInstance() method is not called, the static inner class is not called to instantiate the Singleton.

Deserialization of serializable singleton classes

Above, we analyzed the writing of five singleton patterns together. In fact, they all have the same problem: deserialization of serializable singleton classes.

Thus, we can write the instance object of the singleton class to disk by serialization and then read it back, i.e., deserialize, to get an instance of the singleton class. Even if the constructor is private, there is a way to get a new instance of a singleton class.

So how do you solve this problem? There are two options:

Option 1 uses readResolve(), a private hook function available in the class, to control deserialization of the class. Take a look at the code example.

import java.io.ObjectStreamException; import java.io.Serializable; public class Singleton implements Serializable{ private static final long serialVersionUID = 0L; private static volatile Singleton instance; // Private construct, Class private Singleton() {} public static Singleton getInstance() {if (instance == null) { synchronized (Singleton.class) { if (instance == null) { instance = new Singleton(); } } } return instance; Private Object readResolve() throws ObjectStreamException {return instance; }}Copy the code

Looking at lines 26-28, the hook function returns the instance of our singleton when deserializing the class, avoiding the problem of creating a new instance when deserializing the class.

Scheme 2: this is the sixth way to implement the singleton pattern: enumeration pattern.

6. Use enumeration to implement singletons

We know that enumerations are just like normal classes in Java, that they can have their own properties, that they can have their own methods, and, most importantly, that the creation of an enumeration instance is thread-safe, and that it is a singleton (including deserialization) in any case.

Let’s look at a code example.

public enum Singleton {
    INSTANCE;
    
    public void someMethod(){
    
    }
}
Copy the code

Hahaha, did your jaw drop?

That’s right, enumerating code to implement singletons is that simple!

So how do you get an instance of the singleton class that the enumeration implements? It’s also very simple.

Singleton instance = Singleton.INSTANCE;
Copy the code

Show! A lone star!

Use containers to implement singletons

That’s kind of a different way to write it. Let’s take a look at some code examples, and I’ll leave it to you to judge for yourself.

import java.util.HashMap; import java.util.Map; Public class SingletonManager {private static Map<String, Object> mSingletonMap = new HashMap<>(); private static Map<String, Object> mSingletonMap = new HashMap<>(); Private SingletonManager() {} public static void registerSingleton(String key, String key) Object instance) { if (! mSingletonMap.containsKey(key)) { mSingletonMap.put(key, instance); Public static Object getSingleton(String key) {return mSingletonmap.get (key); }}Copy the code

During program initialization, objects of multiple singletons are injected into the container, and the corresponding singletons are retrieved from the container by key when needed.

The problem is that the initial singleton in the container consumes resources, whether it is used or not.

System-level services such as AMS, WMS, and LayoutInflater are registered in the System as singletons when appropriate. When the corresponding services need to be invoked, GetSystemService (String name).

conclusion

This concludes the writing of the seven singleton patterns described in this article.

In either case, the core idea is to first privatize the constructor and then get an instance of the corresponding class through static methods.

The choice depends on the application scenario of the corresponding singleton class, such as high concurrency environment, JDK version >= 1.5, resource consumption of the singleton class instance, how often the singleton class instance is used, etc.

Usage scenarios for the singleton pattern

Singleton mode has a wide range of applications, and the specific usage scenarios are as follows:

  • The entire project needs a shared access point or needs to share data;

  • Creating objects requires a lot of resources, such as I/O operations and database connections.

  • Tool class objects and so on.

reference

  • Android source code design pattern analysis and combat

  • Android Advanced Light

  • HeadFirst Design Mode

  • Big Talk Design Mode

This article was first published on the public account: niujiaojianhi dancing, ID: Niujiaojianhi, welcome to pay attention to.