设计模式学习-单例模式

定义 单例模式是应用最广泛的设计模式之一。其确保单例类只有一个实例,而且自行实例化并向系统提供这个实例。当创建一个对象需要消耗很多资源时,可以考虑使用单例模式。
UML类图 设计模式学习-单例模式
文章图片
单例模式 单例模式的角色

  • Client
    【设计模式学习-单例模式】客户端类,即单例类的使用者。
  • Singleton
    单例类,向客户端提供单例对象的实例。
单例模式的实现关键点
  1. 私有化单例类的构造函数,确保客户端不能通过new的方式实例化对象。
  2. 一般提供一个静态方法向客户端提供对象的实例。
  3. 确保单例类的对象有且只有一个,尤其在多线程情况下。
单例模式的实现方式 1.饿汉式
饿汉式在定义静态单例对象时就将其初始化,并在静态方法中返回。
/** * 单例 * 饿汉式 */ public class Singleton { //声明静态对象并初始化 private static Singleton instance = new Singleton(); //私有化构造函数 private Singleton(){} //通过静态方法返回实例对象 public static Singleton getInstance() { return instance; } }

2.懒汉式
懒汉式先定义静态单例对象,在单例类第一次使用时初始化对象,getInstance为同步方法,保证了多线程情况下单例对象的唯一性。
/** * 单例 * 懒汉式 */ public class Singleton { private static Singleton instance; private Singleton(){}private static synchronized Singleton getInstance(){ if (instance==null){ instance = new Singleton(); } return instance; } }

该方式的优点是,单例对象在第一次使用时才初始化。其缺点是,每次调用getInstance方法都会进行同步,造成不必要的开销。
3.Double Check Lock(DCL)双重检查锁实现单例
/** * 单例 * 双重检查 */ public class Singleton { private static volatile Singleton instance; private Singleton() {}public static Singleton getInstance() { if (instance == null) { synchronized (Singleton.class) { if (instance == null) { instance = new Singleton(); } } } return instance; } }

DCL是开发中使用最多的实现单例的方式。getInstance中有两次判空,故其名曰双重检查。第一次判空是为了避免不必要的同步,第二次判空为了确保在实例为空的情况下再初始化单例对象。
但由java的指令重排序,可能会导致DCL检查失效,所以静态变量上要加volatile关键字确保安全。
4.静态内部类实现单例
/** * 单例 * 静态内部类实现 */ public class Singleton { private Singleton(){}private Singleton getInstance(){ return SingletonHolder.instance; }private static class SingletonHolder{ private static Singleton instance = new Singleton(); } }

第一次加载Singleton时并不会初始化instance,只有在第一次调用getInstance方法时会导致instance被初始化。这种方式不仅确保了线程安全,也保证了单例对象的唯一性,而且还延迟了单例的初始化。
5.通过枚举实现
/** * 单例 * 定义枚举类实现 */ public enumSingleton { INSTANCE; }

默认枚举实例的创建是线程安全的,在任何情况下它都是一个单例。对于其他几种实现单例的方式在反序列化时会重新创建对象,而枚举不存在这个问题。
6.使用容器实现单例模式
public class SingletonManager { private static HashMap sMap = new HashMap<>(); private SingletonManager() {}public void registerService(String key, Object value) { if (!sMap.containsKey(key)) { sMap.put(key, value); } }public Object getService(String key) { return sMap.get(key); } }

这种方式可以将多种单例类型注入到统一的管理类中,在使用时根据key获取对应的单例类对象,在使用时通过统一的接口获取,降低了用户使用成本。
Android源码中的单例模式 LayoutInflater是Android提供的负责加载布局的服务,它也是一个单例。我们会通过LayoutInflater.from(this)获得它的实例,from方法如下
/** * 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; }

在该方法中通过context的getSystemService来获得LayoutInflater实例,在分析装饰模式时,我们知道Context的实现类是ContextImpl,我们到ContextImpl中找到getSystemService方法
@Override public Object getSystemService(String name) { return SystemServiceRegistry.getSystemService(this, name); }

这里调用了SystemServiceRegistry对象的getSystemService方法
/** * 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; }

在这个方法中首先会获得ServiceFetcher对象,然后从fetcher中获得service对象。SYSTEM_SERVICE_FETCHERS是一个key为String,value为ServiceFetcher的HashMap,那现在的问题就是要搞明白ServiceFetcher对象是如何存到SYSTEM_SERVICE_FETCHERS这个Map中的。SystemServiceRegistry中的registerService方法如下
private static void registerService(String serviceName, Class serviceClass, ServiceFetcher serviceFetcher) { SYSTEM_SERVICE_NAMES.put(serviceClass, serviceName); SYSTEM_SERVICE_FETCHERS.put(serviceName, serviceFetcher); }

就是在这个方法中调用了SYSTEM_SERVICE_FETCHERS的put方法将ServiceFetcher对象存入。而registerService方法又在SystemServiceRegistry的静态代码块中调用,如下
static { //... registerService(Context.LAYOUT_INFLATER_SERVICE, LayoutInflater.class, new CachedServiceFetcher() { @Override public LayoutInflater createService(ContextImpl ctx) { return new PhoneLayoutInflater(ctx.getOuterContext()); }}); //... }

那么当SystemServiceRegistry类加载时,这里的静态代码块执行,并完成各个系统服务的注册,由于静态代码块只执行一次,所以也满足了单例的要求。

    推荐阅读