Kotlin协程核心理解

一、协程是什么?

1.1 基本概念的理解

    我们知道JVM中的线程的实现是依赖其运行的操作系统决定的,JVM只是在上层进行了API的封装,包含常见的有线程的启动方法,状态的管理,比如:Java中抽象出了6种状态,提供了start方法用于启动线程。
    但是线程一旦调用start()开始执行,那我们是很难再控制线程的停止的,尽管jdk中提供了suspend()方法,但是suspend也只是做了标记线程需要中断,最终是否中断,什么时候中断还是依赖操作系统的具体实现逻辑,从语言层面来说是无法直接控制的。

// java线程的状态定义在Java$State枚举对象中
public enum State {
        /**
         * Thread state for a thread which has not yet started.
         */
        NEW,

        /**
         * Thread state for a runnable thread.  A thread in the runnable
         * state is executing in the Java virtual machine but it may
         * be waiting for other resources from the operating system
         * such as processor.
         */
        RUNNABLE,

        /**
         * Thread state for a thread blocked waiting for a monitor lock.
         * A thread in the blocked state is waiting for a monitor lock
         * to enter a synchronized block/method or
         * reenter a synchronized block/method after calling
         * {@link Object#wait() Object.wait}.
         */
        BLOCKED,

        /**
         * Thread state for a waiting thread.
         * A thread is in the waiting state due to calling one of the
         * following methods:
         * <ul>
         *   <li>{@link Object#wait() Object.wait} with no timeout</li>
         *   <li>{@link #join() Thread.join} with no timeout</li>
         *   <li>{@link LockSupport#park() LockSupport.park}</li>
         * </ul>
         *
         * <p>A thread in the waiting state is waiting for another thread to
         * perform a particular action.
         *
         * For example, a thread that has called {@code Object.wait()}
         * on an object is waiting for another thread to call
         * {@code Object.notify()} or {@code Object.notifyAll()} on
         * that object. A thread that has called {@code Thread.join()}
         * is waiting for a specified thread to terminate.
         */
        WAITING,

        /**
         * Thread state for a waiting thread with a specified waiting time.
         * A thread is in the timed waiting state due to calling one of
         * the following methods with a specified positive waiting time:
         * <ul>
         *   <li>{@link #sleep Thread.sleep}</li>
         *   <li>{@link Object#wait(long) Object.wait} with timeout</li>
         *   <li>{@link #join(long) Thread.join} with timeout</li>
         *   <li>{@link LockSupport#parkNanos LockSupport.parkNanos}</li>
         *   <li>{@link LockSupport#parkUntil LockSupport.parkUntil}</li>
         * </ul>
         */
        TIMED_WAITING,

        /**
         * Thread state for a terminated thread.
         * The thread has completed execution.
         */
        TERMINATED;
    }

    而协程内的代码依然执行在线程上,因为线程是CPU调度的基本单元这个大前提还是不变的,属于操作系统层面的基本概念了。但是协程通过使用状态机的方式在语言层面上实现了一种状态、生命周期更易管控的代码逻辑调度框架(语言层面的框架),也可以理解为轻量级线程,并且不像线程那样直接使用操作系统实现的线程,一旦启动基本就只能等任务执行结束或请求中断待能中断时才停止运行。

1.2 协程和线程、进程的关系

进程、线程和协程的关系示意图
启动一个线程执行任务:

val task1 = Thread {
   val result = requestUserInfo()
   println("task1 finished, result = $result")
}
task1.start()

启动一个协程执行任务:

val task1 = launch {
   val result = requestUserInfo()
   println("task1 finished, result = $result")
}
// requestUserInfo()需要切换协程运行的线程需要增加suspend修饰,
// 定义成挂起函数
suspend fun requestUserInfo(): UserInfo = withContext(Dispatchers.IO) {
    delay(500)
    return@withContext UserInfo("10000", "zhangsan")
}

总结一下,协程和线程的区别:

  • 线程一旦开始执行就不会暂停,直到任务结束,这个过程是连续的
  • 协程能够自己挂起和恢复,语言层面实现了挂起和恢复流程,能够实现协作式调度
1.3 使用协程的关键API
1.3.1 协程作用域:CoroutineScope

    创建协程或调用挂起函数必须有协程作用域,kotlin创建作用域有三种办法,GlobalScope、runBlocking和CoroutineScope()方法。
三种协程作用域的用法

  • Android中提供的协程作用域有:
    • MainScope()
      MainScope()

    • lifecycleScope
      在这里插入图片描述
      lifecycleScope中的协程会在Activity销毁时执行cancel

    • viewModelScope
      viewModelScope

1.3.2 协程对象:Job
public interface Job : CoroutineContext.Element {
	// 注(1)
	public companion object Key : CoroutineContext.Key<Job>
	// 如果协程还未启动,比如传入的start对象是LAZY,可通过主动调用
	// start方法启动协程
	public fun start(): Boolean
	// 注(2)
	public fun cancel(cause: CancellationException? = null)
	// 当前协程的子协程
	public val children: Sequence<Job>
	// 附加子协程,使当前协程对象成为父协程
	@InternalCoroutinesApi
    public fun attachChild(child: ChildJob): ChildHandle
    // 等待当前协程执行完成,比如调用协程的cancel()方法后,调用join()
    // 就是等待协程cancel执行完成
    public suspend fun join()
    // 注册在取消或完成此作业时 同步 调用一次的处理程序
    public fun invokeOnCompletion(handler: CompletionHandler): DisposableHandle
    @InternalCoroutinesApi
    public fun invokeOnCompletion(
        onCancelling: Boolean = false,
        invokeImmediately: Boolean = true,
        handler: CompletionHandler): DisposableHandle
}
  • (1)Key : 声明成伴生对象后,只要是同一种类型的Job创建出来的不同Job实例,key都是相同的,也就是同类型的Job对象key也相同
  • (2)cancel(): 取消协程
1.3.3 协程上下文:CoroutineContext

    存放协程相关的一些信息

1.3.4 协程调度器:CoroutineDispatcher
  • Dispatchers.Main: Android中特有的,在主线程中执行协程代码,其他平台使用会抛出异常
  • Dispatchers.IO: 用于IO密集型的协程任务
  • Dispatchers.Default: 用于CPU密集型的协程任务
  • Dispathcers.Unconfined: 不指定协程执行的线程调度器

二、协程在Android中的常见用法

2.1 子线程中执行耗时任务后切到主线程更新UI
// 场景1: 在子线程中执行耗时任务后,切到主线程处理
coroutineScope.launch {
    // 挂起函数,执行时从当前线程中脱离,执行在dispatcher执行的线程中,执行完毕后再切换原来的线程中
    // 挂起后当前协程下一行代码会等待挂起函数执行完成
    val result = withContext(Dispatchers.IO) {
        // 在Dispatchers.IO(线程调度器)指定的子线程中执行下面的代码
        delay(5000)
        100
    }
    Log.d(TAG, "onCreate: main 2 =========> $coroutineContext")
    binding.tvNews.text = result.toString()
}

上面的用法对于Android来说,协程是一个异步代码执行框架,相比于Thread+Handler的方式更加简洁,省去了开发者编写线程切换代码的工作。

2.2 多个耗时任务并行执行合并结果【常见的业务模型】

    在Android业务中我们经常需要并行开始多个业务接口请求,然后合并成一个结果,进行后续业务逻辑的判断、UI的展示,使用Jdk提供的CountDownLatch,RxJava的zip都可以实现类似的功能逻辑。
如下展示了kotlin在这个业务模型中如何实现:

coroutineScope.launch {
    // 在Dispatchers.IO执行的线程中执行任务1
    val async1Result = async(Dispatchers.IO) {
        Log.d(TAG, "onCreate: async1 $coroutineContext")
        executeTask1()
    }
    // 在Dispatchers.IO执行的线程中执行任务2
    val async2Result = async(Dispatchers.IO) {
        Log.d(TAG, "onCreate: async2 $coroutineContext")
        executeTask2()
    }
    // 在调用async方法之后两个协程任务都已经并行跑起来了,这时候调用await方法等待执行结果
    val result = async1Result.await() + async2Result.await()
    Log.d(TAG, "onCreate: async result = $result")
}
  • kotlin中使用async实现类似java中Callable的协程任务,但是await方法阻塞等待结果并没有提供超时时间的参数
  • async()方法是创建一个可获取返回值的协程对象,类型是Deferred,继承自Job

三、挂起函数的理解

3.1 挂起函数的本质
  • 协程的核心是函数或一段程序能够支持挂起,执行完成后又从挂起位置恢复,然后继续执行后面的代码。
  • kotlin的是借助线程实现的,是对线程的一个封装框架,通过launch、async启动一个协程,其实就是启动一个闭包中的代码块。
  • 当执行到suspend函数时,暂时不执行协程代码了,而是从当前线程中脱离,函数内的逻辑转到协程调度器所指定的线程中去执行,等到挂起函数执行完毕后,又恢复都挂起的位置,继续执行后续逻辑

所以总结来说,挂起函数就是切到别的线程,稍后又能够自动切回来的线程调度操作。

3.2 为什么挂起函数一定要在协程或挂起函数中调用?

    挂起函数切到调度器线程中后,是需要协程框架主动调用resumeWith方法再切回来的,如果在非协程非挂起函数调用,那么就没有协程环境,无法切回来,就无法实现挂起的执行逻辑。

四、协程的取消

协程提供了cancel方法进行取消。

class Job {
	public fun cancel(cause: CancellationException? = null)
}

cancel()方法其实还有另外两个重载方法,但是打上了@Deprecated注解,所以不再使用了。

五、协程并发数据同步问题

    在java中多线程访问共享变量时,因为有主内存和工作内存模型的设计。

class Test {
    private var count = 0
    private val mutex = Mutex()
    suspend fun test() = withContext(Dispatchers.IO) {
        repeat(100) {
            launch {
                repeat(1000) {
                   count++
                }
            }
        }
        launch {
            delay(3000)
            println("finished----> count = $count")
        }
    }
}

fun main(): Unit = runBlocking {
    runBlocking {
        Test().test()
    }
}

输出结果:

finished----> count = 97861

如上代码,在循环中创建100个协程,每个协程中对count变量累加1000次,最终count的值应该是100000,但是输出是97861,存在并发修改数据不一致问题。
在Java线程中,我们可以采用sychronized或ReentrantLock方式进程多线程同步处理,保证最终数据的正确性。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/109213.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

windows8080端口占用

查看端口占用 netstat -ano | findstr “8080”查看占用进程 tasklist | findstr “4664”关闭占用进程 taskkill /f /t /im httpd.exe

读图数据库实战笔记03_遍历

1. Gremlin Server只将数据存储在内存中 1.1. 如果停止Gremlin Server&#xff0c;将丢失数据库里的所有数据 2. 概念 2.1. 遍历&#xff08;动词&#xff09; 2.1.1. 当在图数据库中导航时&#xff0c;从顶点到边或从边到顶点的移动过程 2.1.2. 类似于在关系数据库中的查…

操作系统 --- 存储器管理

一、简答题 1.存储器管理的基本任务&#xff0c;是为多道程序的并发执行提供良好的存储器环境。请问好的存储器环境”应包含哪几个方面&#xff1f; 答&#xff1a; 2.内存保护是否可以完全由软件实现&#xff1f;为什么&#xff1f; 答&#xff1a;内存保护的主要任务是确保每…

C语言_断言assert详解

一、assert定义 assert() 的用法像是一种"契约式编程"&#xff0c;在我的理解中&#xff0c;其表达的意思就是&#xff0c;程序在我的假设条件下&#xff0c;能够正常良好的运作&#xff0c;其实就相当于一个 if 语句&#xff1a; if(假设成立) {程序正常运行&…

用户登录前后端开发(一个简单完整的小项目)——SpringBoot与session验证(带前后端源码)全方位全流程超详细教程

&#x1f9f8;注&#xff1a;不要看我的文件多&#xff0c;那是我的其他项目&#xff0c;这个项目所用的文件我会全部用红框框起来&#xff0c;没框的部分不用管&#xff0c;前端两个文件&#xff0c;后端一个文件 &#x1f4dc; 目录 首先&#xff0c;定义前后端交互接口 然…

强化学习中值函数应用示例

一、Gridworld Gridworld是一个用于教授强化学习概念的简化的电子游戏环境。它具有一个简单的二维网格&#xff0c;智能体可以在其中执行动作并获得奖励。这个环境是有限的&#xff0c;因为它有一个明确的开始和结束状态&#xff0c;以及一组确定的动作和奖励。 在Gridworld中&…

use renv with this project create a git repository

目录 1-create a git repository 2-Use renv with this project 今天在使用Rstudio过程中&#xff0c;发现有下面两个新选项&#xff08;1&#xff09;create a git repository (2) Use renv with this project. 选中这两个选项后&#xff0c;创建新项目&#xff0c;在项目目…

NEFU数字图像处理(三)图像分割

一、图像分割的基本概念 1.1专有名词 前景和背景 在图像分割中&#xff0c;我们通常需要将图像分为前景和背景两个部分。前景是指图像中我们感兴趣、要分割出来的部分&#xff0c;背景是指和前景不相关的部分。例如&#xff0c;对于一张人物照片&#xff0c;人物就是前景&…

目标检测算法改进系列之添加EIOU,SIOU,AlphaIOU,FocalEIOU等

YOLOv8添加EIoU,SIoU,AlphaIoU,FocalEIoU,Wise-IoU等 yolov8中box_iou其默认用的是CIoU&#xff0c;其中代码还带有GIoU&#xff0c;DIoU&#xff0c;文件路径&#xff1a;ultralytics/yolo/utils/metrics.py&#xff0c;函数名为&#xff1a;bbox_iou 原始代码 def bbox_i…

故障诊断模型 | Maltab实现LSTM长短期记忆神经网络故障诊断

文章目录 效果一览文章概述模型描述源码设计参考资料效果一览 文章概述 故障诊断模型 | Maltab实现LSTM长短期记忆神经网络故障诊断 模型描述 长短记忆神经网络——通常称作LSTM,是一种特殊的RNN,能够学习长的依赖关系。 他们由Hochreiter&Schmidhuber引入,并被许多人进行了…

2023 年值得关注的国外网络安全初创公司

网络安全初创公司试图解决的问题往往有点超前于主流。他们可以比大多数老牌公司更快地填补空白或新兴需求。初创公司通常可以更快地创新&#xff0c;因为它们不受安装基础的限制。 当然&#xff0c;缺点是初创公司往往缺乏资源和成熟度。公司致力于初创公司的产品或平台是有风…

基于单片机的空气质量检测系统

欢迎大家点赞、收藏、关注、评论啦 &#xff0c;由于篇幅有限&#xff0c;只展示了部分核心代码。 技术交流认准下方 CSDN 官方提供的联系方式 文章目录 概要 一、主要内容二、系统方案设计2.1 系统方案设计2.2 主控制器模块选择 三、 系统软件设计4.1 程序结构分析4.2系统程序…

基于SSM的会员卡管理系统设计与实现

末尾获取源码 开发语言&#xff1a;Java Java开发工具&#xff1a;JDK1.8 后端框架&#xff1a;SSM 前端&#xff1a;采用JSP技术开发 数据库&#xff1a;MySQL5.7和Navicat管理工具结合 服务器&#xff1a;Tomcat8.5 开发软件&#xff1a;IDEA / Eclipse 是否Maven项目&#x…

S32K144芯片焊接完成后使用S32DS初次下载无法下载解决方法

一、问题现象如下&#xff0c;S32DS Debug下报错 二、原因&#xff0c;原厂芯片出厂时的FLASH Memory的安全机制是激活的&#xff0c;仿真器是可以连上&#xff0c;但是没法读取Flash Memory的内容 三、解决方法 参考图示&#xff0c;解锁后即可正常Debug

Mac电脑配置Dart编程环境

1.安装Dart SDK 官网地址&#xff1a;https://dart.dev/get-dart $brew tap dart-lang/dart$brew install dart 安装后&#xff0c;用命令检测一下是否安装正常。 $brew info dart 2.VS Code配置Dart环境 1).安装VS Code 官网地址&#xff1a;https://code.visualstudio.c…

【技能树笔记】网络篇——练习题解析(十)

【技能树笔记】网络篇系列前九篇 【技能树笔记】网络篇——练习题解析&#xff08;一&#xff09;-CSDN博客 【技能树笔记】网络篇——练习题解析&#xff08;二&#xff09;-CSDN博客 【技能树笔记】网络篇——练习题解析&#xff08;三&#xff09;-CSDN博客 【技能树笔记】网…

DAY38 动态规划 + 509. 斐波那契数 + 70. 爬楼梯 + 746. 使用最小花费爬楼梯

动态规划理论 动态规划&#xff0c;Dynamic Programming&#xff0c; DP&#xff0c; 如果某一问题有很多重叠子问题&#xff0c;使用动态规划是最有效的。 所以动态规划中每一个状态一定是由上一个状态推导出来的&#xff0c;这一点就区分于贪心&#xff0c;贪心没有状态推导…

Cross Site Scripting (XSS)

攻击者会给网站发送可疑的脚本&#xff0c;可以获取浏览器保存的网站cookie&#xff0c; session tokens, 或者其他敏感的信息&#xff0c;甚至可以重写HTML页面的内容。 背景 XSS漏洞有不同类型&#xff0c;最开始发现的是存储型XSS和反射型XSS&#xff0c;2005&#xff0c;Am…

Node学习笔记之user用户API模块

1、获取用户的基本信息 步骤 获取登录会话存储的session中用户的id判断是否获取到id根据用户id查询数据库中的个人信息检查指定 id 的用户是否存在将密码设置为空将数据返回给前端 // 获取用户信息数据 exports.userinfo (req, res) > {(async function () {// 1. 获取…

C语言每日一练(二)

单链表经典算法专题 一、 单链表相关经典算法OJ题1&#xff1a;移除链表元素 解法一&#xff1a;在原链表中删除Node.nextnext的节点 typedef struct ListNode ListNode; struct ListNode* removeElements( ListNode* head, int val) {ListNode* pcur head;ListNode* pre h…