Five implementations of the singleton pattern of the Android design pattern

Reprinted:

http://blog.csdn.net/joeleedreamer/article/details/71717379

1. What are design patterns?

By using design patterns, our code can be more reusable and maintainable, and your code can be written more elegantly. There are theoretically 23 design patterns.

The singleton pattern, hence the name, refers to the creation of only one unique instance of a class by setting it statically. The purpose of this setting is to meet the developer's expectations?? This class only needs to be instantiated and created once, and because it is static, the loading speed should also be faster than the normal speed of instantiating a class (in theory).

2. Usage scenarios: 

Ensure that a certain class has one and only one object scene to avoid generating multiple objects that consume too many resources. For example, in an application, there should be only one instance of ImageLoader. This Imageloader contains thread pools, cache systems, network requests, etc., which consume more resources. For example, to access resources such as IO and database, consider using the singleton pattern.

3. Implementing the singleton pattern has the following key points:

(1) Its constructor is not open to the outside world, generally private

(2) Return a singleton class object through a static method or enumeration;

(3) Ensure that there is only one object of the singleton class, especially in multi-threaded scenarios;

(4) Ensure that the singleton class object does not recreate the object when deserializing


advantage:

(1) Since the singleton pattern has only one instance in memory, the memory overhead is reduced, especially when an object needs to be created and destroyed frequently, and the performance cannot be optimized when it is created or destroyed, the advantages of the singleton pattern are very obvious. 
(2) Since only one instance is generated in the singleton mode, the performance overhead of the system is reduced. When the generation of an object requires more resources, such as reading the configuration and generating other dependent objects, it can be used when the application starts. Directly generate a singleton object, and then solve it by permanently resident in memory. 
(3) The singleton mode can avoid multiple occupation of resources, such as a write file operation. Since only one instance exists in the memory, simultaneous write operations to the same resource file are avoided. 
(4) The singleton mode can set global access points in the system to optimize and share resource access. For example, a singleton class can be designed to be responsible for the mapping of all data tables.

shortcoming:

(1) The singleton mode generally has no interface, and it is difficult to expand. If you want to expand, there is basically no second way to achieve it except modifying the code. 
(2) If the singleton object holds the Context, it is easy to cause memory leaks. At this time, it should be noted that the Context passed to the singleton object is preferably the Application Context.



The singleton pattern of the ANDROID design pattern

Hungry Chinese

public class Singleton{

    private static Singleton instance = new Singleton();

    private Singleton(){}

    public static Singleton newInstance(){
        return instance;
    }
}

Hungry Chinese style  is the simplest implementation method. This implementation method is suitable for those situations where singletons are used during initialization. This method is simple and rude. If the initialization of singleton objects is very fast and takes up very little memory, this is the case. This method is more appropriate, and can be loaded and initialized directly when the application starts. However, if the singleton initialization operation takes a long time and the application has requirements for startup speed, or the singleton occupies a large amount of memory, or the singleton is only used in a specific scenario, and generally In this case, it is not appropriate to use the hungry singleton mode when it is not used. In this case, a lazy mode is needed to delay loading singletons on demand.

Advantage: "lazy instantiation" can be achieved.

Disadvantage: In a multithreaded scenario, the object is likely to be instantiated multiple times, which brings up the limitations of the first implementation. There is no guarantee that a singleton can still be maintained in a multithreaded scenario.

lazy

public class Singleton{
    private static Singleton instance = null;

    private Singleton(){}

    public static Singleton newInstance(){
        if(null == instance){
            instance = new Singleton();
        }
        return instance;
    }
}

The biggest difference between the lazy style and the hungry style is that the initialization operation of the singleton is delayed until it is needed, which is very useful in some occasions. For example, a singleton is not used many times, but the functions provided by this singleton are very complex, and loading and initialization consumes a lot of resources. At this time, using lazy style is a very good choice.


Singleton pattern under multithreading

The above introduces some basic application methods of the singleton pattern, but the usage methods mentioned above all have an implicit premise, that is, they are all applied under the condition of single thread, and once they are replaced by multithreading, there will be errors. risks of.

In the case of multi-threading, there will be no problem with the hungry style , because the JVM will only load the singleton class once, but the lazy style may have the problem of repeatedly creating singleton objects. Why is there such a problem? Because the lazy man style is thread-unsafe when creating a singleton, multiple threads may call his newInstance method concurrently, causing multiple threads to create multiple copies of the same singleton.

So is there a way to make the lazy man 's simple interest mode also thread-safe? The answer is definitely yes, that is to achieve it by adding synchronization locks.

lazy synchronization lock

public class Singleton {
 
    private static Singleton instance = null;
 
    private Singleton(){
    }
 
    public static Singleton getInstance() {
        synchronized (Singleton.class) {
            if (instance == null) {
                instance = new Singleton();
            }
        }
 
        return instance;
    }
}

This is the most common way to solve synchronization problems, using synchronization lock synchronized (Singleton.class) to prevent multiple threads from entering at the same time and causing instance to be instantiated multiple times. Here is an example of using this method on Android:

InputMethodManager example

public final class InputMethodManager {
    //内部全局唯一实例  
    static InputMethodManager sInstance;
   
    //对外api  
    public static InputMethodManager getInstance() {
        synchronized (InputMethodManager.class) {
            if (sInstance == null) {
                IBinder b = ServiceManager.getService(Context.INPUT_METHOD_SERVICE);
                IInputMethodManager service = IInputMethodManager.Stub.asInterface(b);
                sInstance = new InputMethodManager(service, Looper.getMainLooper());
            }
            return sInstance;
        }
    }
}  

The above is the singleton usage related to the input method class in the Android source code.

But there is actually a better way as follows:

double check lock

public class Singleton {
 
    private static volatile Singleton instance = null;
 
    private Singleton(){
    }
 
    public static Singleton getInstance() {
        // if already inited, no need to get lock everytime
        if (instance == null) {
            synchronized (Singleton.class) {
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
 
        return instance;
    }
}

可以看到上面在synchronized (Singleton.class)外又添加了一层if,这是为了在instance已经实例化后下次进入不必执行synchronized (Singleton.class)获取对象锁,从而提高性能。

以上两种方式还是挺麻烦的,我们不禁要问,有没有更好的实现方式呢?答案是肯定的。 我们可以利用JVM的类加载机制去实现。在很多情况下JVM已经为我们提供了同步控制,比如:

  • 在static{}区块中初始化的数据

  • 访问final字段时

  • 等等

因为在JVM进行类加载的时候他会保证数据是同步的,我们可以这样实现:

采用内部类,在这个内部类里面去创建对象实例。这样的话,只要应用中不使用内部类 JVM 就不会去加载这个单例类,也就不会创建单例对象,从而实现懒汉式的延迟加载和线程安全。

实现代码如下:

静态内部类

public class Singleton{
    //内部类,在装载该内部类时才会去创建单利对象
    private static class SingletonHolder{
        public static Singleton instance = new Singleton();
    }

    private Singleton(){}

    public static Singleton newInstance(){
        return SingletonHolder.instance;
    }

    public void doSomething(){
        //do something
    }
}

这样实现出来的单例类就是线程安全的,而且使用起来很简洁,麻麻再也不用担心我的单例不是单例了。

然而这还不是最简单的方式,Effective Java中推荐了一种更简洁方便的使用方式,就是使用枚举。

枚举类型单例模式

public enum Singleton{
    //定义一个枚举的元素,它就是Singleton的一个实例
    instance;

    public void doSomething(){
        // do something ...
    }    
}

使用方法如下:

public static void main(String[] args){
   Singleton singleton = Singleton.instance;
   singleton.doSomething();
}

默认枚举实例的创建是线程安全的.(创建枚举类的单例在JVM层面也是能保证线程安全的), 所以不需要担心线程安全的问题,所以理论上枚举类来实现单例模式是最简单的方式。

总结

一般单例模式包含了5种写法,分别是饿汉、懒汉、双重校验锁、静态内部类和枚举。相信看完之后你对单例模式有了充分的理解了,根据不同的场景选择最你最喜欢的一种单例模式吧!




Guess you like

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