设计模式 - 单例模式(Singleton)

1. 概述

In software engineering, the singleton pattern is a software design pattern that restricts the instantiation of a class to one object. This is useful when exactly one object is needed to coordinate actions across the system. The concept is sometimes generalized to systems that operate more efficiently when only one object exists, or that restrict the instantiation to a certain number of objects. The term comes from the mathematical concept of a singleton. – wikipedia

单例模式:是一种对象创建型模式,用来确保程序中一个类最多只有一个实例,并提供访问这个实例的全局点

单例模式解决以下类似问题:

  • 如何样保证一个类只有一个实例?
  • 如何轻松地访问类的唯一实例?
  • 一个类如何控制它的实例化?
  • 如何限制一个类的实例数量?

单例模式的解决方案:

  • 隐藏类的构造方法,用private修饰符修饰构造方法.
  • 定义一个public的静态方法(getInstance()) ,返回这个类唯一静态实例。

2. 适用场景

以下场景可使用单例模式:

  • 某些管理类,保证资源的一致访问性。
  • 创建对象时耗时过多或耗费资源过多,但又经常用到的对象;
  • 工具类对象;
  • 频繁访问数据库或文件的对象。

Android:

  • Context.getSystemService()

  • KeyguardUpdateMonitor.getInstance(mContext)

  • Calendar.getInstance()

  • ….

其他:

  • 日志操作类
  • 文件管理器
  • 数据库的连接尺

3.实现方式

3.1 饿汉式

饿汉式,故名思议,很饿,所以在类加载的时候就直接创建类的实例。

/** * Singleton class. Eagerly initialized static instance guarantees thread safety. */
public final class Singleton {

  /** * Private constructor so nobody can instantiate the class. */
  private Singleton() {}

  /** * Static to class instance of the class. */
  private static final Singleton INSTANCE = new Singleton();

  /** * To be called by user to obtain instance of the class. * * @return instance of the singleton. */
  public static Singleton getInstance() {
    return INSTANCE;
  }
}
复制代码

优点:

  • 多线程安全

缺点:

  • 内存浪费,类加载之后就被创建了实例,但是如果某次的程序运行没有用到,内存就被浪费了。

小结:

  • 适合:单例占用内存比较小,初始化时就会被用到的情况。

  • 不适合:单例占用的内存比较大,或单例只是在某个特定场景下才会用到

3.2 懒汉式

​ 懒汉式,故名思议,很懒,需要用的时候才创建实例。

/** * Singleton class. Eagerly initialized static instance guarantees thread safety. */
public final class Singleton {

  /** * Private constructor so nobody can instantiate the class. */
  private Singleton() {}

  /** * Static to class instance of the class. */
  private static final Singleton INSTANCE = null;

  /** * To be called by user to obtain instance of the class. * * @return instance of the singleton. */
  public static Singleton getInstance() {
    if (INSTANCE == null){
        INSTANCE = new Singleton();
    }
    return INSTANCE;
  }
}
复制代码

优点:

  • 内存节省,由于此种模式的实例实在需要时创建,如果某次的程序运行没有用到,就是可以节省内存

缺点:

  • 线程不安全,分析如下
    线程1线程2INSTANCE
    public static Singleton getInstance() {null
    public static Singleton getInstance() {null
    if (INSTANCE == null){null
    if (INSTANCE == null){null
    INSTANCE = new Singleton();object1
    return INSTANCE;object1
    INSTANCE = new Singleton();object2
    return INSTANCE;object2

    糟糕的事发生了,这里返回2个不同的实例。

小结:

  • 适合:单线程,内存敏感的

  • 不适合:多线程

3.3 线程安全的懒汉式

/** * Thread-safe Singleton class. The instance is lazily initialized and thus needs synchronization * mechanism. * * Note: if created by reflection then a singleton will not be created but multiple options in the * same classloader */
public final class ThreadSafeLazyLoadedSingleton {

  private static ThreadSafeLazyLoadedSingleton instance;

  private ThreadSafeLazyLoadedSingleton() {
  // to prevent instantiating by Reflection call
    if (instance != null) {
        throw new IllegalStateException("Already initialized.");
    }
  }

  /** * The instance gets created only when it is called for first time. Lazy-loading */
  public static synchronized ThreadSafeLazyLoadedSingleton getInstance() {
    if (instance == null) {
        instance = new ThreadSafeLazyLoadedSingleton();
    }
    return instance;
  }
}
复制代码

优点:

  • 多线程安全

缺点:

  • 执行效率低,每个线程在想获得类的实例时候,执行getInstance()方法都要进行同步。而其实这个方法只执行一次实例化代码就够了,后面的想获得该类实例,直接return就行了。方法进行同步效率太低要改进。

小结:

  • 不建议使用此方法,后续介绍其他方法可兼顾内存和多线程安全.

3.4 线程安全的双重检查

/** * Double check locking * <p/> * http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html * <p/> * Broken under Java 1.4. * * @author mortezaadi@gmail.com */
public final class ThreadSafeDoubleCheckLocking {

  private static volatile ThreadSafeDoubleCheckLocking instance;

  /** * private constructor to prevent client from instantiating. */
  private ThreadSafeDoubleCheckLocking() {
    // to prevent instantiating by Reflection call
    if (instance != null) {
      throw new IllegalStateException("Already initialized.");
    }
  }

  /** * Public accessor. * * @return an instance of the class. */
  public static ThreadSafeDoubleCheckLocking getInstance() {
    // local variable increases performance by 25 percent
    // Joshua Bloch "Effective Java, Second Edition", p. 283-284
    
    ThreadSafeDoubleCheckLocking result = instance;
    // Check if singleton instance is initialized. If it is initialized then we can return the // instance.
    if (result == null) {
      // It is not initialized but we cannot be sure because some other thread might have // initialized it
      // in the meanwhile. So to make sure we need to lock on an object to get mutual exclusion.
      synchronized (ThreadSafeDoubleCheckLocking.class) {
        // Again assign the instance to local variable to check if it was initialized by some // other thread
        // while current thread was blocked to enter the locked zone. If it was initialized then // we can 
        // return the previously created instance just like the previous null check.
        result = instance;
        if (result == null) {
          // The instance is still not initialized so we can safely (no other thread can enter // this zone)
          // create an instance and make it our singleton instance.
          instance = result = new ThreadSafeDoubleCheckLocking();
        }
      }
    }
    return result;
  }
}
复制代码

优点:

  • 多线程安全

注意点:

  • jdk 1.5以下多线程安全不能实现

小结:

  • 可使用此方法,兼顾内存和多线程安全.

3.5 静态内部类

public class Singleton {
    private Singleton() {
    }

    /** * 类级的内部类,也就是静态的成员式内部类,该内部类的实例与外部类的实例 * 没有绑定关系,而且只有被调用到时才会装载,从而实现了延迟加载。 */
    public static Singleton getInstance() {
        return SingletonLazyHolder.instance;
    }

    private static class SingletonLazyHolder {
        /** * 静态初始化器,由JVM来保证线程安全 */
        private final static Singleton instance = new Singleton();
    }
}
复制代码

优点:

  • 多线程安全

注意点:

  • jdk 1.5以下多线程安全不能实现

小结:

  • 可使用此方法,兼顾内存和多线程安全.

3.6 枚举

public enum EnumSingleton {

  INSTANCE;

  @Override
  public String toString() {
    return getDeclaringClass().getCanonicalName() + "@" + hashCode();
  }
}
复制代码

小结:

  • 可使用此方法,兼顾内存和多线程安全.同时这个也是Effective Java推荐使用的方法。注意枚举也是jdk 1.5开始加入的。

4.总结

单例占用内存比较小,初始化时就会被用到的情况 – 推荐使用方法 3.1

多线程安全和内存占用大,特定场景下采用,推荐使用方法 3.4.3.5,3.6. 使用时注意jdk的版本。个人推荐使用 3.4.3.5

5.Android代码实例

5.1 Dialer,使用方法3.2

packages/apps/Dialer/java/com/android/incallui/InCallPresenter.java

private static InCallPresenter sInCallPresenter;
/** Inaccessible constructor. Must use getRunningInstance() to get this singleton. */
@VisibleForTesting
InCallPresenter() {}
public static synchronized InCallPresenter getInstance() {
    if (sInCallPresenter == null) {
      sInCallPresenter = new InCallPresenter();
    }
    return sInCallPresenter;
}

//其他无关代码省略
复制代码

5.2 Email,使用方法3.5

packages/apps/Dialer/java/com/android/incallui/InCallPresenter.java

public class NotificationControllerCreatorHolder {
    private static NotificationControllerCreator sCreator =
            new NotificationControllerCreator() {
                @Override
                public NotificationController getInstance(Context context){
                    return null;
                }
            };

    public static void setNotificationControllerCreator( NotificationControllerCreator creator) {
        sCreator = creator;
    }

    public static NotificationControllerCreator getNotificationControllerCreator() {
        return sCreator;
    }

    public static NotificationController getInstance(Context context) {
        return getNotificationControllerCreator().getInstance(context);
    }
}
复制代码

有兴趣的可以自己再找找案例看看。

6.有参数的单例

android上有很多需要Context参数的单例场景。先不要急,看看Android源码的实例:

packages/apps/Email/src/com/android/email/EmailNotificationController.java

    private static EmailNotificationController sInstance;
    /** Singleton access */
    public static synchronized EmailNotificationController getInstance(Context context) {
        if (sInstance == null) {
            sInstance = new EmailNotificationController(context, Clock.INSTANCE);
        }
        return sInstance;
    }
复制代码

其实也很简单吗,但是这里面有个小问题,如果传递参数是敏感的,是需要替换的,那就需要在处理一下:

public final class Singleton {

    private Context context;
    private static volatile Singleton instance;

    private Singleton(Context context) {
        this.context = context;
        if (instance != null) {
            throw new IllegalStateException("Already initialized.");
        }
    }

    public static Singleton getInstance(Context context) {
        Singleton result = instance;
        if (result == null) {
            synchronized (Singleton.class) {
                result = instance;
                if (result == null) {
                    instance = result = new Singleton(context);
                }
            }
            //这里要注意重新赋值
            instance.context = context;
        }
        return result;
    }
}
复制代码

鸣谢

  1. Initialization-on-demand holder idiom
  2. Singleton pattern
  3. Head First 设计模式
  4. java-design-patterns
    原文作者:算法小白
    原文地址: https://juejin.im/post/5a716fce51882573233576c9
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞