android设计模式—单例设计模式(1)

单例模式只允许创建一个对象,因此节省内存,加快对象访问速度,因此对象需要被公用的场合适合使用,如多个模块使用同一个数据源连接对象等等。如:

1.需要频繁实例化然后销毁的对象。

2.创建对象时耗时过多或者耗资源过多,但又经常用到的对象。

3.有状态的工具类对象。

4.频繁访问数据库或文件的对象。

以下都是单例模式的经典使用场景:

1.资源共享的情况下,避免由于资源操作时导致的性能或损耗等。如日志文件,应用配置。

2.控制资源的情况下,方便资源之间的互相通信。如线程池等。

要点


  • 构造函数不对外开放,必须为Private(就是不能用New的形式生成对象)

  • 通过一个静态方法或者枚举返回单例对象

  • 确保单例类的对象有且只有一个,尤其是在多线程环境下

  • 确保单例类对象在反序列化时不会重新创建对象


饿汉单例模式


public class Singleton {

private static final Singleton singleton = new Singleton();

//构造函数私有化

private Singleton() {

}

//公有的静态函数,对外暴露获取单例对象的接口

public static Singleton getInstance() {

return singleton;

}

}

饿汉单例模式采用的是静态变量 + fianl关键字的方式来确保单例模式,应用启动的时候就生成单例对象,效率不高

懒汉模式


public class Singleton {

private static Singleton singleton;

//构造函数私有化

private Singleton() {

}

//公有的静态函数,对外暴露获取单例对象的接口

public static synchronized Singleton getInstance() {

if (singleton == null) {

singleton = new Singleton();

}

return singleton;

}

}

懒汉模式的主要是加了synchronized关键字,每调用一次getInstance方法,都会进行同步,造成了不必要的开销

DCL模式(双重检查锁定模式)


public class Singleton{

// 静态属性,volatile保证可见性和禁止指令重排序

private volatile static Singleton instance = null;

// 私有化构造器

private Singleton(){}

public static Singleton getInstance(){

// 第一重检查锁定

if(instance==null){

// 同步锁定代码块

synchronized(Singleton.class){

// 第二重检查锁定

if(instance==null){

// 注意:非原子操作

instance=new Singleton();

}

}

}

return instance;

}

}

  • DCL模式是使用最多的单例模式,它不仅能保证线程安全,资源利用率高,第一次执行getInstance时单例对象才会实例化;同时,后续调用getInstance方法时又不会有懒汉模式的重复同步的问题,效率更高;在绝大多数情况下都能保证单例对象的唯一性

  • DCL模式需要注意要用volatile关键字,否则还是会导致创建多个实例

  • DCL模式的缺点是第一次加载时由于需要同步反应会稍慢;在低于JDK1.5的版本里由于Java内存模型的原因有可能会失效

volatile

volatile 是DCL模式的关键,他保证了可见性和禁止指令重排序,一般情况下android程序员都会忘记加volatile!!!!

  • 禁止指令重排序。 我们知道new Singleton()是一个非原子操作,编译器可能会重排序【构造函数可能在整个对象初始化完成前执行完毕,即赋值操作(只是在内存中开辟一片存储区域后直接返回内存的引用)在初始化对象前完成】。而线程B在线程A赋值完时判断instance就不为null了,此时B拿到的将是一个没有初始化完成的半成品。

  • 保证可见性。 线程A在自己的工作线程内创建了实例,但此时还未同步到主存中;此时线程B在主存中判断instance还是null,那么线程B又将在自己的工作线程中创建一个实例,这样就创建了多个实例。

什么是指令重排序?有两个层面:

  1. 在虚拟机层面,为了尽可能减少内存操作速度远慢于CPU运行速度所带来的CPU空置的影响,虚拟机会按照自己的一些规则(这规则后面再叙述)将程序编写顺序打乱——即写在后面的代码在时间顺序上可能会先执行,而写在前面的代码会后执行——以尽可能充分地利用CPU。拿上面的例子来说:假如不是a=1的操作,而是a=new byte1024*1024`,那么它会运行地很慢,此时CPU是等待其执行结束呢,还是先执行下面那句flag=true呢?显然,先执行flag=true可以提前使用CPU,加快整体效率,当然这样的前提是不会产生错误(什么样的错误后面再说)。虽然这里有两种情况:后面的代码先于前面的代码开始执行;前面的代码先开始执行,但当效率较慢的时候,后面的代码开始执行并先于前面的代码执行结束。不管谁先开始,总之后面的代码在一些情况下存在先结束的可能。

  2. 在硬件层面,CPU会将接收到的一批指令按照其规则重排序,同样是基于CPU速度比缓存速度快的原因,和上一点的目的类似,只是硬件处理的话,每次只能在接收到的有限指令范围内重排序,而虚拟机可以在更大层面、更多指令范围内重排序。硬件的重排序机制参见《从JVM并发看CPU内存指令重排序(Memory Reordering)》

重排序很不好理解,上面只是简单地提了下其场景,要想较好地理解这个概念,需要构造一些例子和图表,在这里介绍两篇介绍比较详细、生动的文章《happens-before俗解》和《深入理解Java内存模型(二)——重排序》。其中的“as-if-serial”是应该掌握的,即:不管怎么重排序,单线程程序的执行结果不能被改变。编译器、运行时和处理器都必须遵守“as-if-serial”语义。

尾声

以薪资待遇为基础,以发展为最终目标,要在高薪资的地方,谋求最好的发展!

下面是有几位Android行业大佬对应上方技术点整理的一些进阶资料。

《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》点击传送门,即可获取!
rial”语义。

尾声

以薪资待遇为基础,以发展为最终目标,要在高薪资的地方,谋求最好的发展!

下面是有几位Android行业大佬对应上方技术点整理的一些进阶资料。

[外链图片转存中…(img-pTEZLULF-1714642355997)]

《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》点击传送门,即可获取!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值