kotlin协程的理解

简介: kotlin协程的理解

伴生对象:companion object 其实质等同于Java中的单例模式

协程:通常实现是用户态的任务协作式调度

  1. 一段可执行代码
  2. 可挂起/可恢复执行
  3. 概念上与语言无关,协程这个概念于1958年提出

依赖框架:

协程的启动:

1.协程体:协程中要执行的操作,是一个被suspend修饰的lambda表达式

传递末尾的lambda表达式,在Kotlin中有一个约定:

如果函数的最后一个参数是函数,那么作为相应的参数的传入的lambda表达式可以放在圆括号之外

2.协程体类:编译器会将协程体编译成封装协程体操作的匿名内部类

3.协程构建器:用于构建协程的函数:比如launch,async

4.挂起函数:由suspend修饰的函数,挂起函数只能在挂起函数或者协程体中调用,

5.挂起点:一般对应挂起函数被调用的位置

6.续体:Continuation

CoroutineScope:

  1. 是一个接口
  2. 只有一个属性:CoroutineContext(协程上下文),
  3. 是一个作用范围,可以通过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 终止态
目录
相关文章
|
缓存 资源调度 编译器
原来是这样啊!浅谈webpack4和webpack5的区别
相对于webpack4,webpack5内置了很多plugin插件,比如、打包、压缩、缓存
948 1
|
监控 安全 Java
Go语言学习笔记(一)
Go语言学习笔记(一)
163 1
|
前端开发 JavaScript Java
SpringCloudGateway网关服务实现文件上传功能
SpringCloudGateway网关服务实现文件上传功能
497 6
|
监控 Linux 测试技术
【实战技巧】使用inotify实现实时文件监控
`inotify`是Linux内核提供的文件系统监控机制,用于实时捕获文件和目录的创建、删除、移动和修改等事件。通过`inotify_init`初始化,`inotify_add_watch`添加监视点,如`. IN_ACCESS`, `. IN_MODIFY`等,及`inotify_rm_watch`移除监视。示例代码展示了监听指定路径下文件修改事件,当事件发生时打印信息。使用`inotify`能高效地构建实时应用,如文件同步和日志监控,简化系统编程。
1826 117
|
存储 Java 调度
深入探索Java并发编程:ConcurrentSkipListSet的高效使用与实现原理
深入探索Java并发编程:ConcurrentSkipListSet的高效使用与实现原理
|
存储 SQL 网络协议
ClickHouse(05)ClickHouse数据类型详解
ClickHouse是一款分析型数据库,支持基础、复合和特殊数据类型。基础类型包括数值(Int、Float、Decimal)、字符串(String、FixedString、UUID)和时间(DateTime、DateTime64、Date)类型。数值类型如Int8-64和Float32-64,Decimal提供高精度计算。字符串中的FixedString有固定长度,UUID作为主键。时间类型最高精度到秒。复合类型有数组、元组、枚举和嵌套,其中数组和元组允许不同数据类型,枚举节省空间,嵌套类型是多维数组结构。特殊类型如Nullable表示可为空,Domain封装IPv4和IPv6。
887 1
ClickHouse(05)ClickHouse数据类型详解
|
存储 缓存 数据库
后端开发的艺术:打造高效、稳定的服务架构
【8月更文挑战第12天】 在数字化浪潮中,后端开发如同搭建一座桥梁,连接用户与数据的无限可能。本文将带你领略后端开发的精髓,从基础的数据库设计到复杂的系统架构,我们将一步步探索如何构建一个既高效又稳定的后端服务。正如甘地所言,“你必须成为你希望在世界上看到的改变”,在后端的世界里,我们正是那些创造改变的人。让我们开始这段技术之旅,发现后端开发的真正魅力所在。
148 5
|
运维 Cloud Native 持续交付
云原生技术浪潮下的企业数字化转型之路
【7月更文挑战第27天】在数字化时代的洪流中,企业面临着前所未有的挑战与机遇。云原生技术作为推动企业数字化转型的重要力量,其核心价值在于提升业务的灵活性、可扩展性及创新能力。本文将深入探讨云原生技术如何助力企业实现敏捷开发、自动化运维,以及构建弹性、可靠的应用架构,从而在激烈的市场竞争中脱颖而出。
|
Oracle 关系型数据库 MySQL
实时计算 Flink版产品使用问题之整库从mysql同步到StarRocks里面,首次全量是否会对mysql造成大量资源消耗,导致影响业务服务
实时计算Flink版作为一种强大的流处理和批处理统一的计算框架,广泛应用于各种需要实时数据处理和分析的场景。实时计算Flink版通常结合SQL接口、DataStream API、以及与上下游数据源和存储系统的丰富连接器,提供了一套全面的解决方案,以应对各种实时计算需求。其低延迟、高吞吐、容错性强的特点,使其成为众多企业和组织实时数据处理首选的技术平台。以下是实时计算Flink版的一些典型使用合集。
|
XML JSON JavaScript
JSON简介
JSON简介
335 0