转载请标明出处:【顾林海的博客】

个人开发的微信小程序,目前功能是书籍推荐,后续会完善一些新功能,希望大家多多支持!

前言

EventBus能够简化各组件间的通信,让我们的代码书写变得简单,能有效的分离事件发送方和接收方(也就是解耦的意思),能避免复杂和容易出错的依赖性和生命周期问题。关于它的使用方式,同学们可以查看相关文章。

EventBus.getDefault().register(this);

以上是EventBus的注册,很简单,通过getDefault()方法获取EventBus实例,再通过它的register(Object subscriber)方法注册事件接受的类。

我们为什么要去看EventBus的源码呢?一是为了了解它的实现原理,二是学习作者的代码编写时的思想包括用到的设计模式,当然更重要的是学习到大神们的编程思维(知识),进而提升自己的代码水平(工资)。从getDefault()方法开始,代码如下:

public class EventBus {static volatile EventBus defaultInstance;public static EventBus getDefault() {if (defaultInstance == null) {synchronized (EventBus.class) {if (defaultInstance == null) {defaultInstance = new EventBus();}}}return defaultInstance;}}

看到上面的代码-单例,相信大家在很多开源项目中都用到过,比如图片加载框架Picasso和Glide,为什么以上这种创建对象的方式在一些著名的开源框架中被运用,这是我们关注的,
就像上面的代码中,为什么这么写?首先要了解DCL(双重检查锁定)和volatile的相关概念。

DCL(双重检查锁定)

相对于我们前端开发中,很少,甚至都接触不到多线程相关的知识,DCL(双重检查锁定)就是在多线程中,用于延迟初始化来降低初始化类和创建类的开销。在创建对象时,如果对象初始化操作
需要高开销,这时会采用一些延迟化初始化方案,比如DCL(双重检查锁定),比如,下面就是一个非线程安全的延迟初始化对象的实例。

public class Demo {private static Demo mDemo;public static Demo getInstance() {if (null == mDemo) {//1mDemo = new Demo();//2}return mDemo;}
}

其实上面代码并没有什么问题(单线程),但如果在多线程的情况下,就有可能出现问题,我们来分析下问题出在哪里,假设当前有两个线程A和B,并且这两个线程都执行了上面
这段代码,如果A线程执行到代码1,B线程也执行到代码1,此时mDemo引用的对象都为空,接下来B线程执行代码2,对象创建完毕,这时回过头看线程A,由于之前线程
A在判断mDemo引用的对象为空,线程A就会执行到代码2,可以发现mDemo对象创建了两次,造成额外的开销。

如何去解决这个问题呢,可以对这个方法进行同步处理,使用synchronized。代码如下:

public class Demo {private static Demo mDemo;public synchronized static Demo getInstance() {if (null == mDemo) {//1mDemo = new Demo();//2}return mDemo;}
}

使用这种方式在多线程频繁调用的情况下,会导致程序性能的下降,此时DCL(双重检查锁定)登场,代码如下:

public class Demo {private static Demo mDemo;public static Demo getInstance() {if (null == mDemo) {//1synchronized (Demo.class) {//2if (null == mDemo) {//3mDemo = new Demo();//4}}}return mDemo;}
}

通过DCL可以大幅降低synchronized带来的性能开销,在多线程中试图在同一时刻创建对象时,会通过加锁来保证只有一个线程能创建对象,同时在对象创建
完毕后,执行getInstance()方法不需要获取锁,直接返回已创建好的对象。
使用DCL看起来很完美,但遇到重排序的问题时,就会出现错误,比如在代码1处的mDemo不为null时,mDemo引用的对象有可能还没有完成初始化。

重排序

重排序是指编译器和处理器为了优化程序性能而对指令序列进行重新排序的一种手段。编译器和处理器在做重排序时,会遵守数据依赖性,也就是说不会改变
存在数据依赖关系的两个操作的执行顺序,数据依赖性仅仅针对单个处理器中执行的指令序列和单个线程中执行的操作。

回过头看上面代码中的代码4处可以分解为3步:

  • 第一步:分配对象的内存空间
  • 第二步:初始化对象
  • 第三步:设置mDemo指向刚分配的内存地址

当遇到重排序的问题是,第二步和第三步就会重排序,重排序后的顺序如下:

  • 第一步:分配对象的内存空间
  • 第三步:设置mDemo指向刚分配的内存地址
  • 第二步:初始化对象

第二步和第三步重排序并不会改变单线程内的程序执行结果,这里还是以两个线程A和B为例,它们的时间线如下:

  • A1:分配对象的内存空间
  • A3:设置mDemo指向刚分配的内存地址
  • B1:判断mDemo是否为空
  • B2:mDemo不为空,线程B访问mDemo引用的对象
  • A2:初始化对象
  • A4:访问mDemo引用的对象

从线程A和B的时间线中可以看出,执行线程B时,由于线程A进行了重排序,导致mDemo在没有初始化对象时就已经分配了内存地址时,从而线程B执行代码1处
发现mDemo不为空,从而获取了一个未初始化的对象。

volatile

既然DCL的问题已经出现了,那我们总归要解决它,给出的方案是基于volatile的解决方案(JDK5或更高版本)。只需要修改代码如下:

public class Demo {private volatile static Demo mDemo;public static Demo getInstance() {if (null == mDemo) {//1synchronized (Demo.class) {//2if (null == mDemo) {//3mDemo = new Demo();//4}}}return mDemo;}
}

当申明的对象为volatile时,在多线程中重排序会被禁止,从而解决了DCL带来的重排序问题。

volatile的主要作用是使变量在多个线程间可见,首先我们要明白在在线程中创建的变量会被存放在两个堆栈中,分别是:

  • 公共堆栈
  • 线程的私有堆栈

当我们在主线程中设置某个线程中的变量,该线程中的变量会从私有堆栈中获取,而主线中给该线程设置的值被更新到公共堆栈中,这样的话会导致私有
堆栈和公共堆栈数据不同步的问题,通过使用volatile关键字,可以强制的从公共内存中读取变量,从而保持线程间访问某个变量时数据同步,volatile
增加了实例变量在多个线程间的可见性。

volatile的一个比较明显的缺点是不支持原子性,它解决的是变量在多个线程之间的可见性,而synchronized关键字解决的是多个线程间访问资源的同步性。关于
volatile就介绍到这里,感兴趣的同学可以阅读Java并发编程的艺术这本书。

Android之EventBus框架源码解析上(单例模式)相关推荐

  1. Android之EventBus框架源码解析下(源码解析)

    转载请标明出处:[顾林海的博客] 个人开发的微信小程序,目前功能是书籍推荐,后续会完善一些新功能,希望大家多多支持! 前言 EventBus是典型的发布订阅模式,多个订阅者可以订阅某个事件,发布者通过 ...

  2. Android 常用开源框架源码解析 系列 (四)Glide

    一.定义  Glide 一个被google所推荐的图片加载库,作者是bumptech.对Android SDk 最低要求是 API 10  与之功能类似的是Square公司的picasso  二.基本 ...

  3. Android 常用开源框架源码解析 系列 (九)dagger2 呆哥兔 依赖注入库

    一.前言 依赖注入定义 目标类中所依赖的其他的类的初始化过程,不是通过手动编码的方式创建的. 是将其他的类已经初始化好的实例自动注入的目标类中. "依赖注入"也是面向对象编程的 设 ...

  4. Android经典著名的百大框架源码解析(retrofit、Okhttp、Glide、Zxing、dagger等等)

    我们Android程序员每天都要和源码打交道.经过数年的学习,大多数程序员可以"写"代码,或者至少是拷贝并修改代码.而且,我们教授编程的方式强调编写代码的艺术,而不是如何阅读代码. ...

  5. BAT高级架构师合力熬夜15天,肝出了这份PDF版《Android百大框架源码解析》,还不快快码住。。。

    前言 为什么要阅读源码? 现在中高级Android岗位面试中,对于各种框架的源码都会刨根问底,从而来判断应试者的业务能力边际所在.但是很多开发者习惯直接搬运,对各种框架的源码都没有过深入研究,在面试时 ...

  6. android handler2--消息队列源码解析

    android handler2–消息队列源码解析 1.Looper 对于Looper主要是prepare()和loop()两个方法. 首先看prepare()方法 public static fin ...

  7. php 框架源码分析,Laravel框架源码解析之模型Model原理与用法解析

    本文实例讲述了Laravel框架源码解析之模型Model原理与用法.分享给大家供大家参考,具体如下: 前言 提前预祝猿人们国庆快乐,吃好.喝好.玩好,我会在电视上看着你们. 根据单一责任开发原则来讲, ...

  8. Linux驱动修炼之道-SPI驱动框架源码分析(上)

    Linux驱动修炼之道-SPI驱动框架源码分析(上)   SPI协议是一种同步的串行数据连接标准,由摩托罗拉公司命名,可工作于全双工模式.相关通讯设备可工作于m/s模式.主设备发起数据帧,允许多个从设 ...

  9. php manual 反射,Laravel框架源码解析之反射的使用详解

    本文实例讲述了Laravel框架源码解析之反射的使用.分享给大家供大家参考,具体如下: 前言 PHP的反射类与实例化对象作用相反,实例化是调用封装类中的方法.成员,而反射类则是拆封类中的所有方法.成员 ...

最新文章

  1. java编程三月有几天_3月有多少天
  2. 智能实验室-全能优化(Guardio) 4.96.0.850
  3. 移动端数据统计,精细化运营的永动机
  4. opencv videoio无法读取rstp_使用一行Python代码从图像读取文本
  5. Java面向对象编程篇5——枚举
  6. OpenBSD 将迎来原生的 Hypervisor
  7. meta http-equiv(属性详解)
  8. yy直播接口php,api.php · yyboss/phpcms - Gitee.com
  9. 昨晚,谷歌发布了一个可怕的人工智能!
  10. HTML+CSS项目总结(建议学习三周后)
  11. 全网最全-超大模型+分布式训练架构和经典论文
  12. 【tomcat】6、调优
  13. linux怎么进入文件的末尾,如何在Linux中附加到文件的末尾
  14. 文科专业计算机等级吗,文科生如何过计算机等级考试
  15. GMT格式的时间处理 Thu Jan 01 00:00:00 +0000 2009成时间戳
  16. A2B的典型应用-给汽车的车机系统做从设备板
  17. 怎么压缩jpg图片文件大小?jpg图片格式的压缩方法
  18. 用动态规划算法解决TSP问题
  19. 初识python五大特点
  20. 如何使用html代码给文字加边框?

热门文章

  1. sublime text3 怎么配置、运行python_怎么用sublime text 3搭建python 的ide?
  2. windows搭建gcc开发环境(msys2) objdump
  3. python面试题之“该死的for循环系列”(二)
  4. mybatis 配置文件中set丢失逗号
  5. codewars068 - Convert string to camel case
  6. php编写TCP服务端和客户端程序
  7. C#中数组、ArrayList和List三者的区别
  8. Android自己定义组件系列【2】——Scroller类
  9. IOS开发中发送Email的两种方法
  10. 使用FoundationDB高效地将SQL数据映射到NoSQL存储系统中