伴生对象:companion object 其实质等同于Java中的单例模式
协程:通常实现是用户态的任务协作式调度
- 一段可执行代码
- 可挂起/可恢复执行
- 概念上与语言无关,协程这个概念于1958年提出
依赖框架:
协程的启动:
1.协程体:协程中要执行的操作,是一个被suspend修饰的lambda表达式
传递末尾的lambda表达式,在Kotlin中有一个约定:
如果函数的最后一个参数是函数,那么作为相应的参数的传入的lambda表达式可以放在圆括号之外
2.协程体类:编译器会将协程体编译成封装协程体操作的匿名内部类
3.协程构建器:用于构建协程的函数:比如launch,async
4.挂起函数:由suspend修饰的函数,挂起函数只能在挂起函数或者协程体中调用,
5.挂起点:一般对应挂起函数被调用的位置
6.续体:Continuation
CoroutineScope:
- 是一个接口
- 只有一个属性:CoroutineContext(协程上下文),
- 是一个作用范围,可以通过CoroutineScope的扩展函数去创建协程(launch async),当这个作用范围被取消的时候,其内部协程也会被取消;GlobalScope除外
- launch函数返回一个Job,可以通过Job进行管理协程
- 为协程提供一个上下文CoroutineContext
GlobalScope:
- 实现CoroutineScope接口,并且重写了上下文,返回一个EmptyCoroutineContext
- 由object修饰,是一个单例对象,所以生命周期跟随整个应用,无法通过自身取消内部协程
launch函数的三个参数,也就是启动协程的三要素:
- CoroutineContext 协程的上下文
- CoroutineStart 协程的启动模式
- suspend CoroutineScope.() -> Unit 协程体
CoroutineContext 协程上下文 ,这是一个数据集合接口声明,所包含的元素有:
- 协程中Job
- 调度器:CoroutineDispatcher
- 协程名:CoroutineName
- ...
CoroutineContext 的数据结构:链表
CombinedContext是CoroutineContext的一个实现类,也是链表中的具体实现节点,节点包含两个元素,
- element:当前的节点集合元素,
- left :CoroutineContext类型,指向链表的下一个元素
第四行:函数get,一个由operator修饰的操作符重载,对应"[ ]"操作符,通过key获取Element对象
第七行:函数fold,遍历当前集合的每一个Element,并对每一个元素进行opreator操作,将操作后的结果进行累加,以initial为其实开始累加,最终返回一个新的CoroutineContext 上下文
第十六行:函数plus,由operator修饰操作符重载,对应"+"操作符,合并两个CoroutineContext对象中的元素(这个元素可以是实现CoroutineContext 接口的任何对象:Job、CoroutineDispatcher、CoroutineName等等),将合并后的上下文返回,
从函数Plus中,我们可以清晰的看出,CoroutineContext的数据存储方式是一个链表,链表的每个节点是CombinedContext,并且存在拦截器的情况下,拦截器永远是链表的头结点 ,拦截器使用效率很高,这样可以保证更快的读取到拦截器
每个元素在创建的时候都会生成唯一的Key对象(单例模式),所以元素在添加到集合中时(plus)同类元素都会被最后的一个所覆盖
如果存在想相同的key的Element对象,则对其进程"覆盖"(先从集合中移除要plus的Element对象,返回一个移除后的集合,确保当前集合不包含要添加的元素)
以CombinedContext中的minusKey进行理解:
1.当我们在plus一个CoroutineContext元素时,需要对当前的CoroutineContext集合进行移除操作,
2.由于Key的唯一性,链表中不会存在重复的元素结点!首先从头结点的element中根据要添加元素的key进行查询,如果查询结果不为空,则直接返回left(意味着在这个链表存在要添加的元素,并且是当前结点的element,故而可以直接返回left)
3.如果2中的查询结果为空,则继续调用minusKey(递归)直到满足以下条件,退出递归
- 移除结点后与移除前的left一样,那么也就意味着链表中不存在要添加的element,所以直接 返回这个链表
- 移除结点后,链表为空,意味着当前链表只有一个结点,并且该结点中的element与要添加的一样,那么直接返回当前的节点的element
- 当前链表就是一个空链表,那么将第三行代码中的newleft和elememt重新组合
Element:
1.第40行代码:每一个元素的类型是Element,而它又实现了CoroutineContext接口,所以Element即可以是一个集合中的元素,也可以是一个集合
CoroutineStart 是协程的启动模式,存在以下4种模式:
- DEFAULT 立即调度,可以在执行前被取消
- LAZY 需要时才启动,需要start、join等函数触发才可进行调度
- ATOMIC 立即执行,执行前不可以被取消
- UNDISPATCHED 立即在当前线程执行,直到遇到第一个挂起点(可能切线程)
协程体:suspend CoroutineScope.() -> Unit
一个lambda表达式,也就是协程中要执行的代码块,即launch函数的代码块;
为什么使用CoroutineScope扩展函数?
上面讲到,在CoroutineScope中只有一个属性,那就是协程上下文;这样我们可以在协程体中访问协程上下文这个对象
-----------------------------------协程中线程的挂起 和 切换----------------------------------------
Dispatchers:调度器,是协程中提供的线程调度器,用来切换线程,指定协程所运行的线程
源码分析:
DisPatchers中提供了4种类型的调度器:
- Defaul:默认调度器,适合CPU密集型任务调度器,比如逻辑计算;
- Main:UI调度器;
- Unconfind:无限制(无拘束)调度器,对协程执行的线程不做限制,协程恢复时可以在任意线程;
- IO:IO调度器,适合IO密集型任务调度器,比如读写文件,网络请求;
从源码中可以看到,这4种类型的调度器的类型均是:CoroutineDispatchers
CoroutineDispatchers:
继承自AbstractCoroutineContextElement,而AbstractCoroutineContextElement是Element的一个抽象实现类,所以调度器本身也是一个CoroutineContext,也可以存放在CoroutineContext集合中;同时实现了ContinuationInterceptor,一个拦截器接口
1.在上图代码中可以看到:ContinuationInterceptor实现了CoroutineContext.Element接口,所以拦截器也可以作为CoroutineContext集合的一个元素
2.在ContinuationInterceptor中定义了一个伴生对象Key,它的类型是CoroutineContext.Key,作为CoroutineContext集合元素的索引的理由:
- 伴生对象的唯一性
- 通过类型访问集合元素,更直观
3.interceptContinuation:对协程体类对象continuation的一次包装,并返回一个新的Continuation,
CoroutineDispatcher:继承自AbstractCroutineContextElement,同时实现了拦截器接口;
- 说明了调度器的本质也是一个拦截器,在kotlin中所有的调度器都是继承自它来实现的自身调度逻辑
- 调度器同时也可以作为CroutineContext集合中的元素
1.isDispatchNeeded:是否需要线程调度;
2.dispatch:线程调度,让一个runnable对象在指定的线程运行;
3.interceptContinuation:将协程体类对象包装成一个DispatchedContinuation对象;
DispatchedContinuation:使用线程调度器将协程体调度到指定的线程执行
1.实现了续体:Continuation,重写了resumeWith的内部实现逻辑,并且持有线程调度器;
2.有两个属性:
- dispatcher:线程调度器
- continuation:线程体类对象,也就是在包装成DispatchedContinuation时传入的协程体类对象
3.关注:delegate,实质就是DispatchedContinuation对象本身
4.resumeWith:首先通过isDispatchNeeded判断是否需要线程调度;
- 如果需要线程调度,则使用dispatcher#dispatch进行调度,所需要的参数分别是:协程上下文和一个runnable对象(这里传入的this,即表示DispatchedContinuation对象本身,由于其继承自DispatchedTask,继续跟进会发现最终实现了Runnable接口),所以这个runnable会运行在调度的线程上
- 如果不需要调度,则使用resumeWith,
5.runnable:从下面源码中可以看到,在run方法中首先从delegate中取出协程体对象,然后调用协程体的扩展函数resume,实质还是执行resumeWith
Dispatchers.Default默认调度器
dispatcher#dispatch()的实现是在调度器的具体实现类中,我们以Dispatchers.Default进行分析
1.useCoroutinesScheduler:默认情况是ture,所以会构建一个DefaultScheduler
2.IO调度器是Dispatchers.Default内的一个变量,并且它和Default调度器共享CoroutineScheduler线程池。
3.调度器的核心是重写dispatch()进行线程的切换,追溯到父类的dispatch:
ExperimentalCoroutineDispatcher
1.在ExperimentalCoroutineDispatcher中的dispatch的实现是通过调用coroutineScheduler.dispatch(),
2.CoroutineScheduler是一个Kotlin实现的线程池,提供协程运行的线程。
-----------------------------------协程中Worker线程----------------------------------------
Worker存在5种状态:
- CPU_ACQUIRED 获取到cpu权限
- BLOCKING 正在执行IO阻塞任务
- PARKING 已处理完所有任务,线程挂起
- DORMANT 初始态
- TERMINATED 终止态