设计模式学习-单例模式

定义

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

UML类图

设计模式学习-单例模式_第1张图片
单例模式

单例模式的角色

  • 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 enum  Singleton {
    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类加载时,这里的静态代码块执行,并完成各个系统服务的注册,由于静态代码块只执行一次,所以也满足了单例的要求。

你可能感兴趣的:(设计模式学习-单例模式)