定义

确保某个类只有一个实例,而且自行实例化并向整个系统提供这个实例。

应用场景

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

关键点

构造函数不对外开放。

通过静态方法或枚举返回单例类对象。

确保单例类的对象有且只有一个,特别是在多线程环境下。

确保单例类的对象在反序列化时不会重新构造对象。

单例模式实现方式

实现方式比较多,但是都有各自的优缺点。

饿汉模式

饿汉模式是在类加载时就创建好单例类实例。

实现

public class HungerSingleton {

private static HungerSingleton mInstance = new HungerSingleton();

private HungerSingleton() {

}

public static HungerSingleton getInstance() {

return mInstance;

}

}

instance是静态对象,在声明的时候就已经初始化了,保证了对象的唯一性。

优点:

简单明了,不需要关心线程同步的问题。

获取对象比较快,不需要做其他任何工作。

缺点:

类加载时因为需要初始化对象,所以比较慢。

可能会产生很多多余无用的对象。

适用场景

如果单例模式实例在系统中经常会被用到,选择此方式实现单例比较合适。但是如果对象初始化工作复杂且在程序运行过程中不一定会使用到此单例对象,那饿汉模式就不太适合了。

懒汉模式

懒汉模式不同于饿汉模式的是,它是在第一次调用getInstance()时才初始化单例对象。

实现

public class LazySingleton {

private static LazySingleton mInstance;

private LazySingleton() {

}

public static synchronized LazySingleton getInstance() {

if (mInstance == null) {

mInstance = new LazySingleton();

}

return mInstance;

}

}

给getInstance()加了synchronized关键字,这样就能够保证不会有两个线程同时去访问这个方法创建重复对象了,将getInstance()变为同步方法保证了单例对象的唯一性。但是在初始化之后获取对象时都不可避免的会造成同步开销。

优点

节约资源,在使用到时才会初始化单例对象。

缺点

第一次调用反应稍慢,需要加载的时间。

每次调用都会进行同步,造成不必要的同步开销。

DCL模式

DCL优点是既能在使用使才初始化对象,又能保证线程安全,且在初始化之后再调用getInstance()不进行同步锁。

实现

public class DCLSingleton {

private static DCLSingleton mInstance = null;

private DCLSingleton() {

}

public static DCLSingleton getInstance() {

if (mInstance == null) {

synchronized (DCLSingleton.class) {

if (mInstance == null) {

mInstance = new DCLSingleton();

}

}

}

return mInstance;

}

}

外层对instance的判空避免了多余的同步工作,只有在初始化对象时才需要同步。

内层的instance的判空保证了对象的唯一。

DCL失效问题

这种方法暂时还是不安全的,并不能保证一定单例,还是有几率会失败的。

主要原因是new一个对象这个操作并不是原子性的,它会被编译为三条汇编指令,分为三个步骤。但是由于JVM的指令重排,这三个步骤的后两个步骤顺序不定。

分配内存空间。

初始化内存空间。

引用变量指向这块内存空间。

指令重排序是JVM为了优化指令,提高程序运行效率。指令重排序包括编译器重排序和运行时重排序。JVM规范规定,指令重排序可以在不影响单线程程序执行结果前提下进行。

指令重排并没有考虑多线程的情况,所以可能会出现下面的场景。

Thread 1

Thread 2

外层instance判空:yes

-

进入同步代码块

-

内层instance判空:yes

-

为单例对象分配内存空间

-

instance指向这块内存空间

-

-

外层instance判空:no

-

直接返回未初始化完毕的instance对象

初始化这块内存空间

-

这样就会拿到一个未初始化完成的对象了,这就是DCL失效问题。

解决失效问题

在java5之后,具体化了volatile关键字,所以在java5之后,就可以用这个关键字来解决DCL失效问题了。

//使用 volatile修饰 instance

private volatile static DCLSingleton mInstance = null;

volatile防止指令重排序,禁止把new过程的指令与把引用赋值给变量的语句重排序,赋值只发生在new结束之后。这样就不会出现上述的失效问题了。

volatile或多或少会影响性能,但是保证了DCL的正确性。

优点

节约资源,在使用到时才会初始化单例对象。

缺点

第一次调用反应稍慢,需要加载的时间。

会有一定的机率发生问题。在jdk5之后可以使用volatile保证正确性,volatile或多或少会影响性能。

静态内部类

同样也是在第一次调用getInstance()才会初始化单例对象。

实现

第一次调用getInstance()时会导致虚拟机加载SingletonHolder类,初始化单例对象,这种方法不仅保证了线程安全,单例对象的唯一性,也延迟了单例对象的实例化,实现起来还非常简单。

public class StaticInnerSingleton {

private StaticInnerSingleton() {

}

public static StaticInnerSingleton getInstance() {

return SingletonHolder.mInstance;

}

private static class SingletonHolder {

private static final StaticInnerSingleton mInstance = new StaticInnerSingleton();

}

}

在类的初始化期间,JVM会去获取一个锁,这个锁可以同步多个线程对同一个类的初始化,从而保证了单例唯一。

枚举单例

这可能是最简单的单例实现了。枚举和普通类一样可以拥有字段和方法,并且默认枚举实例创建是线程安全的,在仍和时刻都是单例的,并且哪怕是支持反序列化,也不会生成新的单例对象。反序列化不同于普通类,枚举是根据名字去查找存在的对象,而不是重新创建对象。

实现

public enum EnumSingleton {

INSTANCE;

public int a = 2;

public int aaa() {

return a;

}

}

枚举在序列化的时候Java仅仅是将枚举对象的name属性输出到结果中,反序列化的时候则是通****过java.lang.Enum的valueOf方法来根据名字查找枚举对象。

支持反序列化

而上面几种方法,如果需要保证完全的单例,还需要去增加readResolve(),需要做如下修改,拿支持序列化的饿汉模式举例:

public class HungerSingleton implements Serializable {

private static final long serialVersionUID = 1L;

//...

private Object readResolve() throws ObjectStreamException {

return mInstance;

}

直接将单例对象返回而不是重新创建新对象。

集合实现

实现

通过一个map来保存所有的单例对象,这种方式换了一种思路,从单例自身转移到单例保存方式上,提供get()、register()来提供单例和获取单例。

public class MapSingletonManager {

private static Map objMap = new HashMap<>();

private MapSingletonManager() {

}

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);

}

}

这种方法降低了耦合,不会像之前的方法一样将单例逻辑糅杂在类中。但是这种方式需要自己去创建单例对象,并且并不能保证堆中只有一个单例对象,只能保证在使用时,从map中提取出来的是同一个单例对象。

小结

书上推荐DCL和静态内部类的方式实现单例,最后总结一下单例模式的优缺点。

优点

减少了内存开销。

降低了系统性能开销。

避免对资源的多重占用。

可以设置全局访问点,优化和共享资源访问。

缺点

拓展困难,一般没有接口。

单例对象如果持有Context,就容易发生内存泄漏。

如果随便地传入一个Activity的Context,那么无论这个Activity是否还需要,它都因为Context被单例对象持有,所以Activity无法回收,只要项目活着,这个Activity就活着,所以就造成了内存泄漏。所以如果单例中需要Context,就最好传递Application的Context,因为Application的生命周期和应用一样长。

android 单例模式的使用场景,Android设计模式—单例模式相关推荐

  1. android 单例模式的使用场景,android设计模式之单例模式

    (其实设计模式应该从属于java,但是会专门针对android做相应的解释,所以就取名为android设计模式~) 一.单例模式的介绍 单例模式是应用最广的模式之一,在应用这个模式的时候,单例对象的类 ...

  2. Java设计模式及应用场景之《单例模式》

    文章目录 一.单例模式定义 二.单例模式的结构和说明 三.懒汉式和饿汉式的实现 1.懒汉式 2.饿汉式 四.懒汉式和饿汉式的优缺点 五.双重检查加锁方式的实现 六.类级内部类方式的实现 七.枚举方式的 ...

  3. android开发论坛!原生Android开发的路该怎么走?系列篇

    前阵子,我和阿里的薪酬福利专家M同学聊了一下午,M同学做了9年薪酬,和我们吐槽了很多薪酬方面的现象,也道出了少有人关注的薪酬逻辑和常识. 这一次,我又找了一位阿里技术岗位的招聘专家T同学,从他的视角中 ...

  4. Android设计模式——单例模式(Singleton)

    二十三种设计模式分为三大类: 创建型模式,共五种:工厂方法模式.抽象工厂模式.单例模式.建造者模式.原型模式. 结构型模式,共七种:适配器模式.装饰器模式.代理模式.外观模式.桥接模式.组合模式.享元 ...

  5. Android之什么场景该使用单例模式总结

    1.什么是单例模式 单例模式意味着只有一个对象,至于单例模式如何实现就不这里就不多说了,有很多种实现办法. 2.什么时候该使用单例模式?如果不使用单例模式会出现什么问题? 先举2个例子   例子1   ...

  6. android studio列模式,在Android studio 中使用单例模式

    本篇简单介绍如何在Android studio中 使用单例模式和使用注意事项. 单例模式 为什么要使用单例模式? 有一些对象我们只需要一个,只需要一个线程池 .缓存或是只有一台打印机.机器人 .机器人 ...

  7. 设计模式-单例模式及应用场景

    概述 单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一.这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式. 这种模式涉及到一个单一的类,该类负责创建自 ...

  8. android edittext不可复制_精选Android中高级面试题:性能优化,JNI,设计模式

    性能优化 1.图片的三级缓存中,图片加载到内存中,如果内存快爆了,会发生什么?怎么处理? 参考回答:首先我们要清楚图片的三级缓存是如何的: 如果内存足够时不回收.内存不够时就回收软引用对象 2.内存中 ...

  9. Android深入透析之常用设计模式经验谈

    前言: Android开发的设计模式,基本设计思想源于java的设计模式,java的设计模式有N多种,据不完全统计,迄今为止,网络出现最频繁的大概有23种.Java只是一门开发语言,学会并掌握这门语言 ...

最新文章

  1. numpy.argwhere 返回的为索引值的array
  2. 微框架spark--api开发利器
  3. php 接收curl json数据格式,curl发送 JSON格式POST数据的接收,以及在yii2框架中的实现原理【精细剖析】...
  4. activiti 流程图乱码
  5. MySQL逻辑运算符的使用
  6. 2020ICPC(小米邀请赛2) - 2020(二分+贪心)
  7. druid java直接调用_Spring Boot使用Druid连接池的示例代码
  8. 不同电脑 命名管道_电脑键盘上的F1到F12,这些键都有哪些用处?用了5年总算明白了...
  9. 分析设计数据库,优化Java程序
  10. Linux 命令(107)—— systemctl 命令
  11. 如何用 Lua 查询青云的主机
  12. 原生JS JavaScript实现懒加载效果
  13. win10 启用.net 3.5 iis错误0x800f081f
  14. 实现自动WiFi连接
  15. 刹车防抱死系统(ABS)
  16. Spring Boot 2.3.5.RELEASE正式升级,SpringBoot新版本发布
  17. gdb gdbtui
  18. HTML5播放器杂音,HTML5 P5.js使用噪音算法实现的超炫彩虹曲线
  19. js中,转义单双引号
  20. iOS debug神器

热门文章

  1. easyx的基本使用(万字解析)
  2. ubuntu18.04 虚拟机网络配置
  3. java产生随机数实例
  4. js逆向爬虫实战之快手第三方平台之获取登录cookies!
  5. 网络安全现在的就业及前景是如何的?
  6. 专业人才为贵阳大数据建言献计
  7. 数据库学习笔记(1)
  8. matlab使用日记(一)
  9. 四种引用类型在Springboot中的使用
  10. 三大电信运营商携号转网数据_狼烟再起,携号转网之后三大运营商何去何从?剖析三者利弊...