设计模式——单例模式的分析与选择

一、简介

单例模式:不能自由构造对象、在应用中只存在一个实例的情况。

例如:图片加载器中一般都含有这么几个部分:RequestManager(请求管理器)、线程池、Engine(数据获取引擎)、MemoryCache、DiskLRUCache、Transformation(图片处理)、Target等模块,很消耗资源,没有必要也不应该创建多个实例。

写法有多种,下面分析常用的懒汉单例模式的优缺点,并做出最优选择。


二、常用单例模式分析

1. 传统懒汉模式

代码:

public class Singleton1 {
    private static Singleton1 sInstance;

    private Singleton1() {
    }

    public static synchronized Singleton1 getInstance() {
        if (sInstance == null) {
            sInstance = new Singleton1();
        }
        return sInstance;
    }
}

 * 优点:第一次调用getInstance()时才初始化,synchronized保证线程安全

 * 缺点:每次调用getInstance()时都会同步,即使sInstance已经初始化过了,造成不必要同步开销

 * 结论:不推荐


2. DCL(Double Check Lock)

代码:

public class Singleton2 {
    private static Singleton2 sInstance;

    private Singleton2() {
    }

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

getInstance()方法进行了两次判空,第一次是为了避免不必要的同步,第二次则是在null的情况下创建实例。

看起来很好但是存在问题:因为sInstance = new Singleton2()这句不是原子操作(可能会被打断),它可分为3步:

  1. 给sInstance分配内存
  2. 调用构造函数初始化成员变量
  3. 将sInstance指向分配的内存空间(执行完本操作后,sInstance != null)

假设:线程A执行到sInstance = new Singleton2()这步,走的顺序为1->3->2,当执行完3后,线程B直接取走了非空的sInstance(但并没有初始化),使用时就会出错,导致DCL(Double Check Lock)失效

* 优点:懒加载,第一次执行getInstance()时单例对象才会实例化,资源利用率高、效率高;

 * 缺点:高并发时有风险(小概率发生错误);第一次加载稍慢

 * 结论:能满足绝大部分使用场景,推荐使用


3.DCL改善

代码:

public class Singleton3 {
    private volatile static Singleton3 sInstance;

    private Singleton3() {
    }

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

在DCL基础上,将sInstance设为volatile

volatile:1.保证线程本地不会有变量副本,每次都从主内存中读取;2.保证变量的写操作都先行发生在后面对于它的读操作

 * 优点:每次都从主内存中读取sInstance,将DCL中sInstance = new Singleton2()操作原子化,避免错误

 * 缺点:多少影响一点性能

 * 结论:可以选择


4. 静态内部类单例

代码:

public class Singleton4 {
    private Singleton4() {
    }

    private static class Holder {
        private static final Singleton4 INSTANCE = new Singleton4();
    }

    public static Singleton4 getInstance() {
        return Holder.INSTANCE;
    }
}

* 优点:懒加载、线程安全、保证单例对象的唯一性(静态内部类只会被加载一次)

* 缺点:无

* 结论:最佳选择


5. 枚举单例

代码:

public enum Singleton5 {
    INSTANCE
}

* 优点:写法简单

* 缺点:枚举比较占资源,官方不是很推荐

* 结论:不是很推荐


三、结论

最佳选择:  4. 静态内部类单例

第二选择:2. DCL

第三选择:3. DCL改善

(个人看法)

猜你喜欢

转载自blog.csdn.net/weixin_39272004/article/details/80909239
今日推荐