《Android源码设计模式解析与实战》读书笔记(二)——单例模式

单例模式是我们常用的一种模式,我也经常使用,但是却不知道它还是一种设计模式,估计很多初级工程师也跟我一样,只会这一种设计模式吧。

 

第二章 应用最广的模式——单例模式

单例模式是应用最广的模式之一

1.定义

确保某一个类只有一个实例,而且自行实例化并向整个程序提供这个实例。许多时候整个程序只需要拥有一个全局对象,这样有利于我们协调程序整体的行为。

 

2.使用场景

避免产生多个对象消耗过多的资源,或者某种类型的对象只应该有且只有一个。比如创建一个对象需要消耗的资源过多,如要访问 IO 和数据库等资源,这时候就要考虑使用单例模式。

 

3.UML类图

 

4.单例模式关键点

1).构造方法不对外开放,一般为 private。
2).通过一个静态方法或者枚举返回单例类对象。
3).确保单例类的对象有且只有一个,尤其是在多线程环境下。
4).确保单例类对象在反序列化的时候不会重新构建新的对象。
通过私有化单例类的构造方法,使客户端不能通过 new 的形式构造单例类的对象,然后暴露一个公有的静态方法,使客户端可以获取到单例类的唯一对象,在获得这个单例对象的过程中,需要确保线程安全,即在多线程环境下获取单例类对象也是有且只有一个,这也是单例模式中比较困难的地方。

 

5.实现单例模式的六种方式

1).饿汉模式

public class Singleton {
    private static Singleton singleton = new Singleton();


    private Singleton() {
    }


    public static Singleton getInstance() {
        return singleton;
    }
}

 

之所以会被称为饿汉模式,是因为这样的方式在声明静态对象的同时就已经初始化了,无论用户是否需要,都先给创建单例对象了,生怕没有机会创建似的。饿汉模式是线程安全的。

 


2).懒汉模式

 

public class Singleton {
    private static Singleton singleton;


    private Singleton() {
    }


    public static synchronized Singleton getInstance() {
        if (singleton == null) {
            singleton = new Singleton();
        }
        return singleton;
    }
}


之所以会被称为懒汉模式,是因为声明的时候懒得去创建对象,等用户需要的时候再去创建,给 getInstance() 方法添加了synchronized 关键字后,懒汉模式也是线程安全的,如果不添加 synchronized 关键字,则在多线程的情况可能仍会创建多个实例,但是如果加了 synchronized 关键字,那么每次调用 getInstance() 方法,无论单例是否已经实例化都会去进行同步,所以会消耗不必要的资源,这也是懒汉模式的最大问题。
懒汉模式在使用时才会实例化,虽然一定程度上节约了资源,但是正因为这样,所以第一次加载的时候可能反应稍慢,最大的问题还是每次调用 getInstance() 方法都会进行同步,造成不必要的开销,所以一般不建议使用懒汉模式。


3).Double Check Lock(DCL)

public class Singleton {
    private volatile static Singleton singleton;


    private Singleton() {
    }


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


这种模式跟懒汉模式比较,不同点就在于 getInstance() 方法中对 singleton 两次判断是否为空,第一次是为了避免不必要的同步,第二次是为了在 singleton 为空的情况下创建实例。这种模式的优点是资源利用率高,既能在需要单例的时候才实例化,也能避免不必要的同步。
DCL 模式能在绝大多数情况下保证单例对象的唯一性,但是在高并发的环境下也可能例外,虽然概率很小。具体原因暂时还没有去深究,等学得更深了会去了解的。


4)静态内部类单例模式

public class Singleton {
    private Singleton() {
    }


    public static Singleton getInstance() {
        return SingleHolder.singleton;
    }


    private static class SingleHolder {
        private static final Singleton singleton = new Singleton();
    }
}


这种模式既能保证线程安全,也能保证单例对象的唯一性,同时也延迟了单例对象的实例化(即在需要单例的时候才实例化),所以这是推荐使用的单例模式实现方式。


5).枚举单例模式

public enum Singleton {
    SINGLETON;


    public void doSomething() {
        System.out.println("do something");
    }
}


这种模式最大的优点就是写法简单,枚举在 Java 中和普通的类一样,有字段也可以有方法,创建枚举实例默认是线程安全的,而且任何情况下它都是一个单例。
这个任何情况与其他几种方式不同,其他几种方式在某种情况下会重新创建对象,这种情况就是反序列化,什么是反序列化暂时我也没去了解,慢慢深究吧,反正它是一种特殊途径,要避免这种特殊情况,上面四种模式都需要添加如下方法:

    private Object readResolve() throws ObjectStreamException {
        return singleton;
    }

 

但是枚举则不存在这个问题,即使反序列化它也不会重新生成新的实例。


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

public class SingletonManager {
    private static Map<String, Object> objMap = new HashMap<>();


    private SingletonManager() {
    }


    public static void registerSingleton(String key, Object singleton) {
        if (!objMap.containsKey(key)) {
            objMap.put(key, singleton);
        }
    }


    public static Object getSingleton(String key) {
        return objMap.get(key);
    }
}


这是单例模式的一种另类实现,在程序初始时,将多种单例类型注入到一个统一的管理类中,使用时根据 key 来获取对应的单例。这种方式可以管理多种类型的单例,并且使用时可以通过统一接口进行获取单例的操作,降低使用成本,也隐藏了具体实现,降低了耦合。

 

6.总结

单例模式是很常用的一种设计模式,通常没有高并发的情况下,这几种单例模式的实现方式都不会有太大区别,出于效率考虑,一般使用 DCL 方式和静态内部类实现单例方式。
单例模式的优缺点:
优点:
1).减少了不必要的内存开支,特别是一个对象频繁的创建和销毁时。
2).减少了系统开销,特别是一个对象需要较多资源时,如产生其他依赖对象、读取配置等。
3).避免对资源的多重占用,比如写文件操作时,由于只有一个实例,就避免对同一个资源文件同时进行写操作。
4).可以在系统设置全局的访问点,优化和共享资源访问。
缺点:
1).单例模式一般没有接口,扩展很困难,想扩展基本上只能修改代码

2).单例模式如果持有Context,则很容易引发内存泄漏,所以传递给单例对象的Context最好是ApplicationContext。

 

Demo下载

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值