美文网首页
设计模式学习-单例模式

设计模式学习-单例模式

作者: m1Ku | 来源:发表于2018-12-16 16:09 被阅读0次

    定义

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

    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 enum  Singleton {
        INSTANCE;
    }
    

    默认枚举实例的创建是线程安全的,在任何情况下它都是一个单例。对于其他几种实现单例的方式在反序列化时会重新创建对象,而枚举不存在这个问题。

    6.使用容器实现单例模式

    public class SingletonManager {
        private static HashMap<String, Object> 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 <T> void registerService(String serviceName, Class<T> serviceClass,
            ServiceFetcher<T> 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<LayoutInflater>() {
                @Override
                public LayoutInflater createService(ContextImpl ctx) {
                    return new PhoneLayoutInflater(ctx.getOuterContext());
                }});
    //...
    }
    

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

    相关文章

      网友评论

          本文标题:设计模式学习-单例模式

          本文链接:https://www.haomeiwen.com/subject/rllxkqtx.html