Android开发-设计模式-单例模式(Singleton)

介绍

        单例模式是对象的创建模式,单例模式确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。这个类称为单例类。

使用场景

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

实现单例模式的关键点

  • 构造函数不对外开放,一般为private。
  • 通过一个静态方法或者枚举返回单例类对象。
  • 确保单例类的对象有且只有一个,尤其是在多线程环境下。
  • 确保单例类对象在反序列化时不会重新构建对象(非必须,若是确定单例类不会序列化,可不需要 implements Serializable )。

实现方式

单例模式的实现方式有6种:
1:饿汉模式
2:懒汉模式
3:Double Check Lock(DCL)
4:静态内部类
5:枚举
6:容器

饿汉模式

/**
 * 单例模式-饿汉模式
 * <p>
 * 实现单例模式的关键点
 * 1:构造函数不对外开放,一般为private。
 * 2:通过一个静态方法或者枚举返回单例类对象。
 * 3:确保单例类的对象有且只有一个,尤其是在多线程环境下。
 * 4:确保单例类对象在反序列化时不会重新构建对象(非必须,若是确定单例类不会序列化,可不需要 implements Serializable )。
 */
public class SingletonDemo implements Serializable {

    private static final SingletonDemo INSTANCE = new SingletonDemo();

    // 私有构造函数
    private SingletonDemo() {
    }

    // 静态方法,返回实例
    public static SingletonDemo getInstance() {
        return INSTANCE;
    }

    /*
     * 如果单例类支持序列化,需要重写readResolve,确保单例类对象在反序列化时不会重新构建对象
     * readResolve方法可在类ObjectInputStream中找到说明
     */
    private Object readResolve() throws ObjectStreamException {
        return INSTANCE;
    }
}

        饿汉模式其实是一种比较形象的称谓。不管程序是否需要这个对象的实例,总是在类加载的时候就先创建好实例,理解起来就像不管一个人想不想吃东西都把吃的先买好,如同饿怕了一样。

        饿汉模式是典型的空间换时间,当类装载的时候就会创建类的实例,不管你用不用,先创建出来,然后每次调用的时候,就不需要再判断,节省了运行时间。

懒汉模式

/**
 * 单例模式-懒汉模式
 * <p>
 * 实现单例模式的关键点
 * 1:构造函数不对外开放,一般为private。
 * 2:通过一个静态方法或者枚举返回单例类对象。
 * 3:确保单例类的对象有且只有一个,尤其是在多线程环境下。
 * 4:确保单例类对象在反序列化时不会重新构建对象(非必须,若是确定单例类不会序列化,可不需要 implements Serializable )。
 * 有书籍《Java与模式》认为懒汉模式更符合Java语言本身特点。
 */
public class SingletonDemo implements Serializable {

    private static SingletonDemo sInstance = null;

    // 私有构造函数
    private SingletonDemo() {
    }

    // 静态方法,返回实例
    public static synchronized SingletonDemo getInstance() {
        if (null == sInstance) {
            sInstance = new SingletonDemo();
        }
        return sInstance;
    }

    /*
     * 如果单例类支持序列化,需要重写readResolve,确保单例类对象在反序列化时不会重新构建对象
     * readResolve方法可在类ObjectInputStream中找到说明
     */
    private Object readResolve() throws ObjectStreamException {
        return sInstance;
    }
}

        懒汉模式其实是一种比较形象的称谓。如果一个对象使用频率不高,占用内存还特别大,明显就不合适用饿汉式了,这时就需要一种懒加载的思想,当程序需要这个实例的时候才去创建对象,就如同一个人懒的饿到不行了才去吃东西。

        懒汉模式是典型的时间换空间,第一次加载时需要实例化,反应稍慢。每次getInstance()都进行同步,造成不必要的同步开销。

Double Check Lock(DCL)

public class SingletonDemo {

    // volatile
    private static volatile SingletonDemo sInstance = null;

    // 私有构造函数
    private SingletonDemo() {
    }

    // 静态方法,返回实例
    public static SingletonDemo getInstance() {
    	// 第一重检测主要是为了避免不必要的同步
        if (null == sInstance) {
            synchronized (SingletonDemo.class) {
            	// 第二重检测则是为了在null的情况下创建实例
                if (null == sInstance) {
                    sInstance = new SingletonDemo();
                }
            }
        }
        return sInstance;
    }
}

        “DCL”,优点,资源利用率高,第一次执行getInstance()时单例对象才会被实例化,效率高。缺点:第一次加载时反应慢,也由于Java内存模型的原因偶尔会失败。不建议在高并发场景或是JDK6版本下使用。
         DCL失效原因,这里推荐2篇文章,介绍了DCL失效原因,点击这里点击这里

静态内部类

public class SingletonDemo {

    // 私有构造函数
    private SingletonDemo() {
    }

    // 静态方法,返回实例
    public static SingletonDemo getInstance() {
        return SingletonDemoHolder.INSTANCE;
    }

    // 静态内部类
    private static class SingletonDemoHolder {
        private static final SingletonDemo INSTANCE = new SingletonDemo();
    }
}

        “静态内部类单例模式”,优点:不仅能够确保线程安全,也能够保证单例对象的唯一性,同时也延迟了单例的实例化。推荐使用该方式来实现单例模式。

枚举

public enum SingletonDemo {
    INSTANCE;

    public void doSomething() {
        // 执行你的逻辑
    }
}

        “枚举单例模式”,枚举实例的创建是线程安全的,并且在任何情况下它都是一个单例。注意,枚举比静态变量多消耗两倍的内存。

容器

public class SingletonDemo {

    private static final Map<String, Object> objMap = new HashMap<String, Object>();

    // 私有构造函数
    private SingletonDemo() {
    }

    public static void registerService(@NotNull String key, @NotNull Object instance) {
        // 这里没有校验key、instance,实际使用中请校验
        if (!objMap.containsKey(key)) {
            objMap.put(key, instance);
        }
    }

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

        “容器单例模式”,在程序的初始,将多种单例类型注入到一个统一的管理类中,在使用时根据key获取对象对应类型的对象。这种方式使得我们可以管理多种类型的单例,并且在使用可以通过统一的接口继续获取操作,降低了用户的使用成本,也对用户隐藏了具体实现,降低了耦合度。
        容器单例模式是GoF为了克服饿汉单例类及懒汉单例均不可继承的缺点而设计的。

单例类的状态

        一个单例类可以是有状态的(stateful),一个有状态的单例对象一般也是可变(mutable)单例对象。
        有状态的可变的单例对象常常当做状态库使用。比如一个单例对象可以持有一个int类型的属性,用来给一个系统提供一个数值唯一的序列号码,作为某个贩卖系统的账单号码。
        当然,一个单例类可以持有一个聚集,从而允许存储多个状态。

没有状态的单例类

        另一方面,单例类也可以是没有状态的(stateless),仅用做提供工具性函数的对象。既然是为了提供工具性函数,也就没有必要创建多个实例,因此使用单例模式很合适。一个没有状态的单例类也就是不变(Immutable)单例类。关于不变模式,可以阅读这篇博文,点击这里

单例只是相对的

        对于有状态的单例类,在不同JVM、不同类加载器、反射都会使之失效。因此在会使单例类失效的环境下,应尽量使用没有状态的单例类。下面介绍“防止反射破坏单例”

/**
 * 防止反射破坏单例
 */
public class SingletonDemo {

    private static boolean sFlag = false;

    // 私有构造函数
    private SingletonDemo() {
        // 防止反射破坏单例
        synchronized (SingletonDemo.class) {
            if (!sFlag) {
                sFlag = true;
            } else {
                throw new RuntimeException("不允许重复创建SingletonDemo的实例");
            }
        }
    }

    // 静态方法,返回实例
    public static SingletonDemo getInstance() {
        return SingletonDemoHolder.INSTANCE;
    }

    // 静态内部类
    private static class SingletonDemoHolder {
        private static final SingletonDemo INSTANCE = new SingletonDemo();
    }
}

模式的优点和缺点

优点:

  • 单例模式会阻止其他对象实例化其自己的单例对象的副本,从而确保所有对象都访问唯一实例。
  • 因为类控制了实例化过程,所以类可以灵活更改实例化过程。
  • 减少内存开支,减少了系统性能开销,避免对某个资源重复占用。

缺点:

  • 单例模式一般没有接口,扩展很困难,若要扩展,除了修改代码基本上没有第二种途径可以实现。
  • 在Android中,单例对象如果持有Context,View等 容易引发内存泄露。

备注:
        在《Java与模式》中还介绍了“不完全的单例类”、“默认实例模式”。感兴趣的童鞋可自行搜索。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值