【Android设计模式应用】 谈谈Android中的单例模式(2)

class KLazilySingleton private constructor() : Serializable {
fun doSomething() {
println(“do some thing”)
}
companion object {
private var mInstance: KLazilySingleton? = null
get() {
return field ?: KLazilySingleton()
}

@JvmStatic
@Synchronized//添加synchronized同步锁
fun getInstance(): KLazilySingleton {
return requireNotNull(mInstance)
}
}
//防止单例对象在反序列化时重新生成对象
private fun readResolve(): Any {
return KLazilySingleton.getInstance()
}
}

DCL(double check lock)改造懒汉式单例
  • 线程安全的单例模式直接是使用synchronized同步锁,锁住getInstance方法,每一次调用该方法的时候都得获取锁,但是如果这个单例已经被初始化了,其实按道理就不需要申请同步锁了,直接返回这个单例类实例即可。于是就有了DCL实现单例方式

  • 而且在kotlin中,可以支持线程安全DCL的单例,可以说也是非常非常简单,就仅仅3行代码左右,那就是Companion Object + lazy属性代理,一起来看下吧

class KLazilyDCLSingleton private constructor() : Serializable {//private constructor()构造器私有化

fun doSomething() {
println(“do some thing”)
}

private fun readResolve(): Any {//防止单例对象在反序列化时重新生成对象
return instance
}

companion object {
//通过@JvmStatic注解,使得在Java中调用instance直接是像调用静态函数一样,
//类似KLazilyDCLSingleton.getInstance(),如果不加注解,在Java中必须这样调用: KLazilyDCLSingleton.Companion.getInstance().
@JvmStatic
//使用lazy属性代理,并指定LazyThreadSafetyMode为SYNCHRONIZED模式保证线程安全
val instance: KLazilyDCLSingleton by lazy(LazyThreadSafetyMode.SYNCHRONIZED) { KLazilyDCLSingleton() }
}
}

静态内部类单例
  • DCL虽然在一定程度上能解决资源消耗、多余synchronized同步、线程安全等问题,但是某些情况下还会存在DCL失效问题
  • 在多线程环境下一般不推荐DCL的单例模式。所以引出静态内部类单例实现

class KOptimizeSingleton private constructor(): Serializable {//private constructor()构造器私有化
companion object {
@JvmStatic
fun getInstance(): KOptimizeSingleton {//全局访问点
return SingletonHolder.mInstance
}
}

fun doSomething() {
println(“do some thing”)
}

private object SingletonHolder {//静态内部类
val mInstance: KOptimizeSingleton = KOptimizeSingleton()
}

private fun readResolve(): Any {//防止单例对象在反序列化时重新生成对象
return SingletonHolder.mInstance
}
}

枚举单例
  • 枚举单例实现,就是为了防止反序列化,因为我们都知道枚举类反序列化是不会创建新的对象实例的
  • 枚举类型的序列化机制保证只会查找已经存在的枚举类型实例,而不是创建新的实例

enum class KEnumSingleton {
INSTANCE;

fun doSomeThing() {
println(“do some thing”)
}
}

Java中的几种单例模式

懒汉式(线程不安全)

//懒汉式单例类.在第一次调用的时候实例化自己
public class Singleton {
//私有的构造函数
private Singleton() {}
//私有的静态变量
private static Singleton single=null;
//暴露的公有静态方法
public static Singleton getInstance() {
if (single == null) {
single = new Singleton();
}
return single;
}
}

  • 一般来说懒汉式分为三个部分,私有的构造方法,私有的全局静态变量,公有的静态方法
  • 起到重要作用的是静态修饰符static关键字,我们知道在程序中,任何变量或者代码都是在编译时由系统自动分配内存来存储的,而所谓静态就是指在编译后所分配的内存会一直存在,直到程序退出内存才会释放这个空间,因此也就保证了单例类的实例一旦创建,便不会被系统回收,除非手动设置为null。
  • 这种方式创建的缺点是存在线程不安全的问题,解决的办法就是使用synchronized 关键字,便是单例模式的第二种写法了。
懒汉式(线程安全)

public class Singleton {
//私有的静态变量
private static Singleton instance;
//私有的构造方法
private Singleton (){};
//公有的同步静态方法
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}

  • 这种单例实现方式的getInstance()方法中添加了synchronized 关键字,也就是告诉Java(JVM)getInstance是一个同步方法。
  • 同步的意思是当两个并发线程访问同一个类中的这个synchronized同步方法时,一个时间内只能有一个线程得到执行,另一个线程必须等待当前线程执行完才能执行,因此同步方法使得线程安全,保证了单例只有唯一个实例。
  • 但是它的缺点在于每次调用getInstance()都进行同步,造成了不必要的同步开销。这种模式一般不建议使用。
饿汉式(线程安全)

//饿汉式单例类.在类初始化时,已经自行实例化
public class Singleton {
//static修饰的静态变量在内存中一旦创建,便永久存在
private static Singleton instance = new Singleton();
private Singleton (){}
public static Singleton getInstance() {
return instance;
}
}

  • 饿汉式在类创建的同时就已经创建好一个静态的对象供系统使用,以后不再改变,所以天生是线程安全的。其中instance=new Singleton()可以写成:

static {
instance = new Singleton();
}

  • 属于变种的饿汉单例模式,也是基于classloder机制避免了多线程的同步问题,instance在类装载时就实例化了。
DCL双重校验模式

public class Singleton {
private static Singleton singleton; //静态变量
private Singleton (){} //私有构造函数
public static Singleton getInstance() {
if (singleton == null) { //第一层校验
synchronized (Singleton.class) {
if (singleton == null) { //第二层校验
singleton = new Singleton();
}
}
}
return singleton;
}
}

  • 这种模式的特殊之处在于getInstance()方法上,其中对singleton进行了两次判断是否空,第一层判断是为了避免不必要的同步,第二层的判断是为了在null的情况下才创建实例。

具体我们来分析一下: 假设线程A执行到了singleton = new Singleton(); 语句,这里看起来是一句代码,但是它并不是一个原子操作,这句代码最终会被编译成多条汇编指令,它大致会做三件事情:

  1. 给Singleton的实例分配内存
  2. 调用Singleton()的构造函数,初始化成员字段
  3. 将singleton对象指向分配的内存空间(即singleton不为空了)

但是在JDK1.5之后,官方给出了volatile关键字,将singleton定义的代码,为了解决DCL失效的问题。

private volatile static Singleton singleton; //使用volatile 关键字

静态内部类单例模式

public class Singleton {
private Singleton (){} ;//私有的构造函数
public static final 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 Singleton { //enum枚举类
INSTANCE;
public void whateverMethod() {

}
}

枚举单例模式最大的优点就是写法简单,枚举在java中与普通的类是一样的,不仅能够有字段,还能够有自己的方法,最重要的是默认枚举实例是线程安全的,并且在任何情况下,它都是一个单例。即使是在反序列化的过程,枚举单例也不会重新生成新的实例。而其他几种方式,必须加入如下方法:

private Object readResolve() throws ObjectStreamException{
return INSTANCE;
}

这样的话,才能保证反序列化时不会生成新的方法

使用容器实现单例模式

public class SingletonManager {
private static Map<String, Object> objMap = new HashMap<String,Object>();//使用HashMap作为缓存容器
private Singleton() {
}
public static void registerService(String key, Objectinstance) {
if (!objMap.containsKey(key) ) {
objMap.put(key, instance) ;//第一次是存入Map
}
}
public static ObjectgetService(String key) {
return objMap.get(key) ;//返回与key相对应的对象
}
}

  • 在程序的初始,将多种单例模式注入到一个统一的管理类中,在使用时根据key获取对应类型的对象。

场景应用

  1. 那么什么时候需要考虑使用单例模式呢?
  • 系统只需要一个实例对象,如系统要求提供一个唯一的序列号生成器或资源管理器,或者需要考虑资源消耗太大而只允许创建一个对象。
  • 客户调用类的单个实例只允许使用一个公共访问点,除了该公共访问点,不能通过其他途径访问该实例。
  1. 下面我们结合Android中一些源码来分析一下下

Android中常用的EventBus框架

  • 我们可以看看EventBus中的如何使用单例模式的,主要是使用双重检查DCL

总结

其实要轻松掌握很简单,要点就两个:

  1. 找到一套好的视频资料,紧跟大牛梳理好的知识框架进行学习。
  2. 多练。 (视频优势是互动感强,容易集中注意力)

你不需要是天才,也不需要具备强悍的天赋,只要做到这两点,短期内成功的概率是非常高的。

对于很多初中级Android工程师而言,想要提升技能,往往是自己摸索成长,不成体系的学习效果低效漫长且无助。

以上就是总结的关于在面试的一些总结,希望对大家能有些帮助,除了这些面试中需要注意的问题,当然最重要的就是刷题了,这里放上我之前整理的一份超全的面试专题PDF

还有 高级架构技术进阶脑图、Android开发面试专题资料,高级进阶架构资料 帮助大家学习提升进阶,也节省大家在网上搜索资料的时间来学习,也可以分享给身边好友一起学习。

【Android核心高级技术PDF文档,BAT大厂面试真题解析】

这里只是整理出来的部分面试题,后续会持续更新,希望通过这些高级面试题能够降低面试Android岗位的门槛,让更多的Android工程师理解Android系统,掌握Android系统。喜欢的话麻烦点击一个喜欢在关注一下~

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

槛,让更多的Android工程师理解Android系统,掌握Android系统。喜欢的话麻烦点击一个喜欢在关注一下~

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

  • 9
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值