(随记七)Android设计模式解析与实战_六种单例模式的实现方案的区别 :
定义 : 确保该类只有一个实例,并且自行实例化向整个系统提供这个实例 .
简单来说就是 : 该类只能被创建一次,并且抛出该类的对象给使用者.使用者无论使用多少次,都只会创建一个该类对象
使用场景 :
- 确保该类有且只有一个对象 , 避免产生多个对象消耗过多的资源 .
- 该类型对象只应该有一个.
- 举例 : 创建一个对象需要消耗的资源过多(如访问io和数据库等资源) , 那该对象就要考虑使用单例模式.
示例图
- 角色介绍 :
- Client —– 高层客户端
- Singleton —- 单例类
- 实现单例模式关键点 :
- 构造函数不对外开放 , 一般为private .
- 通过一个静态方法或者枚举返回单例对象 .
- 确保单例类对象有且只有一个 , 尤其是在多线程的情况下 .
- 确保单例类对象在反序列化时不会重新构建对象 .
- 通过将单例类的构造函数私有化,使得客户端代码不能通过 new 的形式手动构造单例类的对象 . 单例类会暴露一个公有静态方法 , 客户端需要这个静态方法获取到单例类的一个对象
- 注意 : 在获取改单例对象的过程中 , 需要保证线程安全 , 即在多线程中也只创建一个对象 .
核心 : 构造方法私有化 , 通过静态方法返回该单例类对象 .
单例的多种实现方式与比较 :
在下叙几种实现单例的方法中 , 除了枚举 , 其他方式在一个情况下都会出现重新创建的情况 , 那就是反序列化 .
- 通过序列化将一个单例的实例对象写到磁盘 , 然后再读回来 , 从而有效的获得一个实例 . 即使构造函数是私有的 , 反序列化时依然可以通过特殊的途径去创建类的一个新的实例 , 相当与调用该类的构造函数 .
反序列化提供了一个很特别的钩子函数 , 类中具有一个私有的 、 被实例化的方法 readResolve(), 这个方法可以让我们控制对象的反序列化 , 例如 , 下述几个示例中如果要杜绝反序列化时重新生成对象需要加上如下方法 :
private Object readRsolve() throws ObjectStreamException { return mLazySingleton; }
饿汉式 :
package com.yt.SingletonPattern;
/**
* author : YiTao
* Created by TaoyYi on 2016/12/19.
* describe : 饿汉式单例
*/
public class EagerSingleton {
private static EagerSingleton mEagerSingleton = new EagerSingleton();
//私有构造方法
private EagerSingleton() {
}
//公有的静态方法 , 对外暴露单例对象接口
public static EagerSingleton getEagerSingleton() {
return mEagerSingleton;
}
}
- 从代码中可以看出 , 该类不能通过new来创建 , 只能通过 EagerSingleton.getEagerSingleton() 函数来获取 , 而这个 mEagerSingleton 是静态对象并且在声明的时候就进行了初始化 , 这就保证了 mEagerSingleton 的唯一性 .
- 核心在于 : 将 EagerSingleton 私有化 , 使得外部方法不能通过构造函数来创建 EagerSingleton 的对象 ,而 EagerSingleton 类通过一个静态方法返回构造对象 .
缺点 : 声明时便已经初始化 .
懒汉式 :
package com.yt.SingletonPattern;
/**
* author : YiTao
* Created by TaoyYi on 2016/12/19.
* describe : 懒汉式单例
*/
public class LazySingleton {
private static LazySingleton mLazySingleton;
private LazySingleton() {
}
public static synchronized LazySingleton getLazySingleton() {
if (mLazySingleton == null) {
mLazySingleton = new LazySingleton();
}
return mLazySingleton ;
}
}
优点 : 只有在使用时才初始化 .
缺点 : 每次使用 getLazySingleton() 时都会同步 , 浪费不必要的同步开销 .
- 在 getLazySingleton() 中添加了 synchronized 关键字 , 也就是说 getLazySingleton 是个同步方法(也就是上面说的多线程中也保证只创建一个对象) , 这就导致即使 mLazySingleton 已经被初始化 , 所以每次调用 getLazySingleton 都会进行同步 ,造成不必要的同步开销 .
DCL(双层检查锁定) :
package com.yt.SingletonPattern;
/**
* author : YiTao
* Created by TaoyYi on 2016/12/19.
* describe : DCL实现单例
*/
public class DCLSingleton {
private volatile static DCLSingleton mDCLSingleton;
private DCLSingleton() {
}
/**
* 这里进行了两次判断 , 第一次判断保证了只有在 mDCLSingleton 为空的时候才使用同步锁 , 第二次判断保证了多线程下也只创建一个对象 .
*
* @return
*/
public DCLSingleton getDCLSingleton() {
if (mDCLSingleton == null) {
synchronized (DCLSingleton.class) {
if (mDCLSingleton == null) {
mDCLSingleton = new DCLSingleton();
}
}
}
return mDCLSingleton;
}
}
- 为什么需要 volatile 这个关键字?
- 假设 线程A 执行到了 mDCLSingleton = new DclSingleton(); , 这里看起来是一句代码 , 但实际上他并不是一个原子操作 , 这句话最终会被编译成多条汇编指令 , 它大致做了3件事情 :
- 给 DCLSingleton 的实例分配内存
- 调用 DCLSingleton()构造函数 , 初始化成员字段 。
- 将 mDCLSingleton 对象指向分配的内存空间 .(此时mDCLSingleton就已经不是null了)
- 但是 , 由于 java 编辑器允许处理器乱序执行 , 以及 JDK1.5 之前 JMM 中 Cache 、 寄存器 到主内存会写顺序规定 , 上面的 2/3 顺序是无法保证的 , 假设 线程A 先执行了 1/3 在 2 未执行之前 , 被切换到了 线程B 上 , 这个时候 mDcLSingleton 已经是非空了 , 所以 线程B 直接取走了 mDcLSingleton 这就导致出错了, 这就是DCL失效问题 .
- 在 jdk 1.5 之后 SUN 官方已经注意到了该问题 , 调整了jvm , 具体化了 volatile 关键字 , 如果是 jdk1.5之后 , 只需要加上 volatile 关键字 就可以保证 mDCLSingleton 对象每次都是从主内存中读取 .
- 假设 线程A 执行到了 mDCLSingleton = new DclSingleton(); , 这里看起来是一句代码 , 但实际上他并不是一个原子操作 , 这句话最终会被编译成多条汇编指令 , 它大致做了3件事情 :
优点 : 资源利用率高 , 第一次执行 getDCLSingleton() 时才会实例化对象 , 效率高 .
缺点 : 第一次加载时反应稍慢 .
静态内部类单例 :
package com.yt.oop;
/**
* author : YiTao
* Created by Taoy on 2016/12/20.
* describe : 静态内部类单例
*/
public class StaticInteriorSingleton {
private StaticInteriorSingleton() {
}
public static StaticInteriorSingleton getmStaticInteriorSingleton() {
return StaticInteriorHolder.mStaticInteriorSingleton;
}
static class StaticInteriorHolder {
private static final StaticInteriorSingleton mStaticInteriorSingleton = new StaticInteriorSingleton();
}
}
优点 : 当第一次加载 StaticInteriorSingleton 类时 , 并不会初始化 mStaticInteriorSingleton , 只有在第一次调用 getmStaticInteriorSingleton 时才会导致 mStaticInteriorSingleton 被初始化 . 因此第一次调用 getmStaticInteriorSingleton 才会导致虚拟家加载 StaticInteriorHolder类 , 这种方式不仅能够保证线程安全 , 也保证了单例对象的唯一性 , 同时也延迟了单例的实例化 .
枚举单例 :
package com.yt.oop;
import android.util.Log;
/**
* author : YiTao
* Created by Taoy on 2016/12/20.
* describe : 枚举实现单例
*/
public enum SingletonEnum {
INSTANCE;
public void doSomething() {
Log.e("--------------", "--------------");
}
}
优点 : 简单 并且在任何情况下(包括反序列化)它都是一个单例 .
使用容器实现单例模式 :
package com.yt.oop;
import java.util.HashMap;
import java.util.Map;
/**
* author : YiTao
* Created by Taoy on 2016/12/20.
* describe : 使用容器实现单例(单例的管理者)
*/
public class SingletonManager {
private SingletonManager() {
}
private static Map<String, Object> objMap = new HashMap<String, Object>();
public static void registerService(String key, Object instance) {
if (!objMap.containsKey(key)) {//如果不包含
objMap.put(key, instance);
}
}
public static Object getInstance(String key) {
return objMap.get(key);
}
}
优点 : 可以管理多种类型的单例
- 在程序的初始 , 将多种单例类型注入到一个统一的管理类中 , 在使用时根据key获取对象对应类型的对象 . 这种方式使我们可以管理多种类型的单例 , 并且在使用时可以通过统一的接口进行获取操作 , 降低了用户(使用者)的使用成本 , 也对用户隐藏了具体实现 , 降低了耦合度 .