设计模式之单例模式的几种写法
单例模式是初级程序员知道的唯一的设计模式,但是就是一个简单的单例模式也有很多的写法,而且不同的写法有不同的优缺点,我们开发使用时可能看到别人怎么写,我们就怎么写,实际上还是有很多原理性的东西需要我们去学习,了解不同写法的原理和优缺点。
单例模式介绍:
单例模式是应用最广的模式之一,应用时,单例对象的类必须保证只有一个实例存在。
定义:
确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。
使用关键点:
1.构造函数不对外开放,一般为private;
2.通过一个静态方法或者枚举返回单例类对象;
3.确保单例类的对象有且只有一个,尤其在多线程环境下;
4.确保单例类对象在反序列化时不会重新构建对象。
饿汉模式
声明静态对象时就已经初始化。这句话其实就说明即使此类并未被使用,空间也已经被分配了。
public class Singleton {
private static Singleton instance = new Singleton();
private Singleton() {
}
public static Singleton getInstance() {
return instance;
}
}
懒汉模式
public class Singleton {
private static Singleton instance;
private Singleton() {
}
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
优点:
单例只在使用时才会被实例化,在一定程度上节约了资源;
缺点:
第一次加载时需要及时进行实例化,反应稍慢,最大的问题是每次调用getInstance都进行同步,造成不必要的同步开销。
不建议使用。
Double Check Lock(DCL)实现单例
优点:
既能够在需要时才初始化单例,又能保证线程安全,且单例对象初始化后调用getInstance不进行同步锁。
public class Singleton {
private static Singleton instance = null;
private Singleton() {
}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
两次判空:
第一层判断主要是为了避免不必要的同步,
第二层判断则是为了在null的情况下创建实例。
由于instance = new Singleton()并不是一个原子操作,大概分三步:
1.给Singleton的实例分配内存;
2.调用Singleton的构造函数,初始化成员字段;
3.蒋instance对象指向分配的内存空间。
但由于编译器允许处理器乱序执行,实际上高并发时也会出现DCL失效问题。
优点:
资源利用率高,第一次执行getInstance时单例对象才会被实例化,效率高。
缺点:
第一次加载时反应稍慢,也由于Java内存模型的原因偶尔会失败。
总结:
使用最多的单例实现方式,能够在需要时才实例化单例对象,并且能够在绝大多数的场景下保证单例对象的唯一性。
静态内部类单例模式
public class Singleton {
private Singleton() {
}
public static Singleton getInstance() {
return SingletonHolder.instance;
}
private static class SingletonHolder {
private static final Singleton instance = new Singleton();
}
}
第一次加载Singleton类时并不会初始化instance,只在第一次调用Singleton的getInstance时才会导致instance被初始化。第一次调用getInstance方法会导致虚拟机加载SingletonHolder类,这种方式不仅能确保线程安全,也能保证单例对象的唯一性,同时也延迟了单例的实例化,所以这是推荐使用的单例实现方式。
枚举单例
public enum SingletonEnum {
INSTANCE;
public void doSomething() {
//
}
}
写法简单是枚举单例的最大优点,不仅能够有字段,还能有自己的方法。最重要的是默认枚举实例的创建是线程安全的,并且在任何情况下它都是一个单例。
由于之前的所有单例模式形式在反序列化的情况下它们会重新创建对象。而枚举方式不会。
使用容器实现单例模式
public class SingletonManager {
private static Map<String, Object> objMap = new HashMap<>();
private SingletonManager() {
}
public static void registerService(String key, Object instance) {
if (!objMap.containsKey(key)) {
objMap.put(key, instance);
}
}
public static Object getService(String key) {
return objMap.get(key);
}
}
将多种单例类型注入到一个统一的管理类中,在使用时根据key获取对象对应类型的对象。此方式可以管理多种类型的单例,并且可以通过统一的接口进行获取操作,降低了用户的使用成本,也对用户隐藏了具体实现,降低了耦合度。
这是Android系统源码中常用的单例模式,例如系统的ServiceManager获取各种service的方式。
总结
推荐用DCL和静态内部类实现单例。
针对单例模式
优点:
减少了内存开支,特别是一个对象需要频繁的创建、销毁时,而且创建和销毁时性能又无法优化。
减少性能开销;
避免对资源的多重占用;
可以在系统设置全局访问点,优化和共享资源访问。
缺点:
没有接口,扩展困难;
单例对象如果持有Context,容易引发内存泄露,此时需注意传递给单例对象的Context最好是Application Context。
此为读书笔记。
参考资料:
《Android 源码设计模式解析与实战》
上一篇: 油猴脚本那些事
下一篇: 油猴插件和脚本的安装流程