Kotlin,简直是 Java 的 Pro Max!(笔记4 协程篇)

目录

Kotlin 协程

为什么引入协程?

协程和线程有什么区别?

协程的使用

依赖

GlobalScope.launch

runBlocking

创建多个协程

delay

suspend 关键字

coroutineScope

job

async

withContext 函数


Kotlin 协程


为什么引入协程?

1. 本质上,协程是轻量级的线程 —— Kotlin 官方文档.

2. 协程就是一个轻量单线程通过快速切换来执行不同的任务,从而实现了非阻塞的并发执行 —— 我的总结

3. 你也可以这样理解:协程对应的是线程池的 newSingleThreadExecutor 类型

在传统的线程模型中,如果一个线程需要等待某个操作完成,那么他就会阻塞在那里,直到操作完成.  这样一来,阻塞期间,线程就不能去执行其他任务,造成了资源的浪费.  而协程不会阻塞住,他可以通过 delay函数 暂时挂起当前任务去执行其他任务,等待操作完成后再回来继续执行.

协程和线程有什么区别?

  • 调度级别:线程是操作系统级别的并发执行单位,由操作系统负责调度和管理.  协程则是用户态的轻量级线程,由程序自身控制切换,不受操作系统的直接调度(每创建一个协程,都有一个内核态的线程动态绑定,用户态下实现调度,切换,而真正执行任务还是内核线程).
  • 资源消耗:线程创建和销毁的成本相对较高,会涉及到用户态到内核态的转变.  而协程创建和销毁的成本较低,因为他们几乎是用户态的模拟线程操作.
  • 控制方式(调度速度):线程是抢占式的,切换都有操作系统来决定,并且线程上下文的切换也会带来一定的开销,因此调度速度较慢.   而协程是协作式的,由程序主动控制何时挂起和恢复执行,没有线程切换的开销,调度速度更快.
  • 并发性:线程可以充分利用多核 CPU 的并发计算能力,实现真正的并发执行.  而协程主要适用于 IO 密集型任务,通过非阻塞的方式提高程序的并发性能.

Kotlin 协程的核心竞争力在于:可以让单个线程,实现类似于 IO 多路复用机制(一个线程同时管理多个 Socket 通道).

什么是一个线程同时管理多个 Socket 通道?

举个例子,我小时候生活的那个小区门口,有很多小摊,早上的时候,家里人一般都起不来,不想自己做饭,那么一般就是由我来给我爸妈买饭,但是呢,他们又吃不到一块去,我妈喜欢吃面皮,俺爸就喜欢吃肉夹馍,而我喜欢吃炒面,那我买早饭就有一下三种策略:

  1. 我自己去买,先买面皮,等面皮好了以后再去买肉夹馍,等肉夹馍好了以后再去买炒面,当然啦,这种效率是最低的~
  2. 我们三个人一起去买,我去买炒面,我妈去买面皮、我爸去买肉夹馍,这样,虽然效率大大提升了,但是系统开销大了~
  3. 我自己去买,先去买面皮,等的过程中再去买肉夹馍,再等的过程中去买炒面,然后这三份哪个先好了,对应的老板就可以喊我一嗓子(epoll 事件通知/回调机制),此时就能让我一个线程同时做三件事,但前提是,这三件事的交互不频繁,大部分时间都在等~  如果这三件事都是交互特别频繁的(比如联机游戏、线上直播、上传视频...)还是多搞几个线程靠谱,一个线程就忙不过来了~

协程的使用

依赖

        <dependency><groupId>org.jetbrains.kotlinx</groupId><artifactId>kotlinx-coroutines-core</artifactId></dependency>

GlobalScope.launch

a)GlobalScope.launch 可以创建一个顶层线程,这种协程当应用程序运行结束,也会跟着一起结束. 

如下代码:

fun main() {GlobalScope.launch {println("hi~")}Thread.sleep(1000)
}

这种写法存在问题,如果 lambda 中的代码不能再 1s 之内结束,就会被强制中断.

b)delay 函数是一个非阻塞式的挂起函数,他只会挂起当前协程,并不会影响到其他协程的运行. 这里使用 delay() 函数,模拟一个耗时 1500ms 的任务,如下:

fun main() {GlobalScope.launch {println("hello")delay(1500)println("world")}Thread.sleep(1000)
}

运行结果如下:

有没有什么办法能让应用程序在协程中的所有代码都运行完了之后再结束呢?当然有,往下看~

runBlocking

runBlocking 函数同样会创建一个协程作用域,它可以保证在协程作用域内所有代码和子协程没有全部执行完之前一直阻塞当前线程.  但需要注意的是,runBlocking 通常只是在测试环境下使用,正式环境使用会产生一些性能上的问题.

fun main() {runBlocking {println("hello")delay(1500)println("world")}Thread.sleep(1000)
}

运行结果如下:

创建多个协程

通过上述案例,貌似没有体会到什么协程到底有什么特别之处?这是因为所有代码都是运行在统一个协程中,一旦涉及到高并发的应用场景,协程相比于线程的优势就体现出来了.

a)我们可以通过在当前协程的作用域下创建子协程的方式来创建多个协程. 子协程的特点就是如果外层作用域的协程结束了,该作用域下的所有子协程也会一同结束,如下:

fun main() {runBlocking {launch {println("hello1")delay(1000)println("world1")}launch {println("hello2")delay(1000)println("world2")}}
}

运行结果如下:

b)另外也可以通过 repeat 创建多个协程

    runBlocking {repeat(100) {launch {val num = Random().nextInt(100)println("hi~ $num")}}}

delay

a)为何引入:随着业务的发展,可能需要协程来处理一些耗时任务,但是又不希望整个耗时的任务阻塞住整个协程.

如下代码,会因为任务A 耗时,阻塞整个协程.

fun main() {runBlocking {//执行一个耗时的任务launch {println("任务 A 开始处理")Thread.sleep(3000)println("任务 A 处理完成")}//执行一个耗时较短的任务launch {println("任务 B 开始处理")Thread.sleep(300)println("任务 B 处理完成")}}
}

执行结果如下:

 b)为了解决这个问题,就可以通过 delay 函数挂起当前协程,让他先去执行其他任务,当其他任务执行完成之后,就会看 delay 设置的时间是否到了,如果到时间了,就会在回来,继续从上次挂起的地方继续执行.

fun main() {runBlocking {//执行一个耗时的任务launch {println("任务 A 开始处理")delay(500) //等待另一个耗时短的任务处理完成,再来处理这个耗时任务Thread.sleep(3000)println("任务 A 处理完成")}//执行一个耗时较短的任务launch {println("任务 B 开始处理")Thread.sleep(500)println("任务 B 处理完成")}}
}

suspend 关键字

随着业务的发展,launch 函数中的代码越来越复杂,需要将部分代码封装到一个函数中,但是这个函数是没有 launch 协程作用域的,怎么使用像 delay() 这样的挂起函数呢?

为此 Kotlin 提供了 suspend 关键字,用来声明此次函数是挂起函数,就可以在此函数中使用 delay 了.

suspend fun longTimeTask() {println("任务 A 开始处理")delay(500)println("任务 A 处理完成")
}

Ps:

  1. 标记:suspend 只是一个标记,本身并不实现挂起功能,只是用来提醒开发者.  真正实现挂起的是 suspend 修饰的函数中的 delay 函数.
  2. 使用范围:suspend 只能再协程体或者其他挂起函数中调用.

但是 suspend 关键字只能将一个函数声明成挂起函数,是无法给他提供协程的作用域的,比如在 hi() 函数中是不可以调用 launch 函数的,因为 launch 函数要求必须在协程作用域中才能调用.这个问题可以借助 coroutineScope 函数来解决~

coroutineScope

coroutineScope 函数也是一个挂起函数,因此可以在其他挂起函数中调用. 

a)他的主要特点是会继承外部协程的作用域并创建一个子协程,因此就可以给挂起函数提供协程作用域了,如下:

suspend fun longTimeTask() = coroutineScope {launch {println("任务 A 开始处理")delay(500)println("任务 A 处理完成")}
}

b)另外 coroutineScope 函数可以保证其作用域内的所有代码和子协程在全部执行完之前,一直挂起外部协程.  如下:

fun main() {runBlocking {coroutineScope {launch {println("开始执行任务 A")delay(1000)println("任务 A 执行完成")}}launch {println("开始执行任务 B")println("任务 B 执行完成")}}
}

执行结果如下:

由此可见,coroutineScope 函数确实是将外部协程挂起了(delay 只在 coroutineScope 作用域内会挂起,让出线程给作用域内的其他任务)只有当它作用域内的所有代码和子协程都执行完毕之后,coroutineScope 函数以外的代码才能得到运行。

看上去 coroutineScope 和 runBlocking 函数作用类似,但是 coroutineScope 函数只会阻塞当前协程,不影响其他协程,也不影响任何线程,因此不会造成性能上损失.  而 runBlocking 会挂起外部线程(上述栗子中就挂起了 main 线程),因此就有可能造成页面卡死的情况,因此实际项目中不推荐使用.

job

前面讲到 runBlocking 会阻塞线程,因此只建议在测试环境下使用.  而 GlobalScope.launch 由于每次创建的都是顶层协程,也不建议使用(作用域广,不好管理),除非你就是明确要创建顶层协程.

a)不管是 GlobalScope.launch 函数还是 launch 函数,他们都会返回一个 job 对象,只需要调用 Job 对象的 cancel 方法就可以取消协程,如下:

    val job = GlobalScope.launch {//处理}job.cancel()

b)由于 GlobalScope 作用域太广,不方便管理,因此实际项目中更常见的使用方式如下:

    val job = Job()val scope = CoroutineScope(job)scope.launch {//处理}job.cancel()

CoroutineScope() 函数会返回一个 CoroutineScope 对象,有了 CoroutineScope 对象之后,就可以随时调用它的 launch 来创建一个协程.

并且所有调用 CoroutineScope 的 launch 函数所创建的协程,都会被关联在 Job 对象的作用域下.  这样只需要调用依次 cancel() 方法,就可以将统一作用域内的所有协程全部取消,大大降低管理成本

c)launch 函数只能用于执行一段逻辑,并且返回值永远是一个 Job 对象.  有没有什么办法可以创建一个协程并获取到他的执行结果呢?async 就可以实现~

async

a)async 用来创建一个子协程,传入一个 Lambda,调用后立即执行.

async 创建的子协程的返回值是 Deferred,通过调用 await 就可以获取到 Lambda 表达式的结果.

如下代码:

    runBlocking {val result = async {println("进行了一些处理")1 + 1}.await()println("拿到处理结果: $result") //输出 2}

b)值得注意的是调用 await 会立即阻塞当前协程,直到当前协程返回结果.

错误的写法如下:

    runBlocking {val startTime = System.currentTimeMillis()val v1 = async {delay(1000)1 + 1}.await()val v2 = async {delay(1000)2 + 2}.await()val endTime = System.currentTimeMillis()println("耗时: ${endTime - startTime}")}

 

这种写法,实际上就是用了双倍的时间,因为 await 会阻塞当前协程,导致串行执行.

正确的写法因该是延缓 await 调用,如下:

    runBlocking {val startTime = System.currentTimeMillis()val v1 = async {delay(1000)1 + 1}val v2 = async {delay(1000)2 + 2}val r1 = v1.await()val r2 = v2.await()val endTime = System.currentTimeMillis()println("耗时: ${endTime - startTime}")}

分析原因:

我们知道,协程本质上是一个单线程快速切换,实现任务完成.

延缓 await 调用中:首先这个线程先遇到 delay 挂起 1s,挂起的期间发现还有一个任务,因此就切换到第二个 async,又遇到 delay 挂起 1s.  此时线程又会挂起. 由于两个async 中的 delay 的时间也相同,因此等待 1s后  就会执行剩下的任务,而剩下的任务都是不耗时的任务,因此时间就差不多是 1s.

而第一种错误的方式:由于直接调用了 await,因此就会阻塞住这个线程,使得整个线程会串行执行,先执行完第一个 async ,然后再执行第二个 async.

withContext 函数

withContext 是一个挂起函数,可以理解为 async + await 的简化版写法.

当调用 withContext 之后,会立即执行代码块中的代码,同时将外部协程挂起.  当 Lambda 中的代码执行完后,会将最后一行的执行结果作为函数的返回值返回.

如下代码:

    runBlocking {val result = withContext(Dispatchers.Default) {1 + 1}println(result)}

上述和 async { 1 + 1 }.await 这种写法唯一不同的就是,withContext 强制要求我们指定一个线程参数.

主要有三个参数可选:

Dispatchers.Default:表示会使用一种默认低并发的线程策略,当你要执行的代码属于计算密集型任务时,开启过高的并发反而可能会影响任务的运行效率

Dispatchers.IO:表示会使用一种较高并发的线程策略,当你要执行的代码大多数时间是在阻塞和等待中,比如说执行网络请求时,为了能够支持更高的并发数量

Dispatchers.Main:表示不会开启子线程,而是在Android主线程中执行代码,但是这个值只能在Android项目中使用

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

GESP图形化编程二级认证真题 2024年3月

GESP 图形化二级试卷 &#xff08;满分&#xff1a;100 分 考试时间&#xff1a;120 分钟&#xff09; 一、单选题&#xff08;共 10 题&#xff0c;每题 3 分&#xff0c;共 30 分&#xff09; 1、小杨的父母最近刚刚给他买了一块华为手表&#xff0c;他说手表上跑的是鸿…

蓝桥杯2023省赛:矩阵总面积|模拟、数学(几何)

题目链接&#xff1a; 0矩形总面积 - 蓝桥云课 (lanqiao.cn) 说明&#xff1a; 参考文章&#xff1a;矩形总面积计算器&#xff1a;计算两个矩形的总面积&#xff0c;包括重叠区域_矩形r1的左下角坐标为x1, yl 、宽度为w1、高度为h1, 矩形r2的左下角坐标为x2,y2、宽-CSDN博客…

React 系列 之 React Hooks(一) JSX本质、理解Hooks

借鉴自极客时间《React Hooks 核心原理与实战》 JSX语法的本质 可以认为JSX是一种语法糖&#xff0c;允许将html和js代码进行结合。 JSX文件会通过babel编译成js文件 下面有一段JSX代码&#xff0c;实现了一个Counter组件 import React from "react";export defau…

【OJ比赛日历】快周末了,不来一场比赛吗? #03.23-03.29 #16场

CompHub[1] 实时聚合多平台的数据类(Kaggle、天池…)和OJ类(Leetcode、牛客…&#xff09;比赛。本账号会推送最新的比赛消息&#xff0c;欢迎关注&#xff01; 以下信息仅供参考&#xff0c;以比赛官网为准 目录 2024-03-23&#xff08;周六&#xff09; #7场比赛2024-03-24…

Unity InputField实现框自适应内容简便方法

要实现InputField框自适应输入内容&#xff0c;除了通过代码进行处理&#xff0c;还可以是使用以下简便的方法。 1、创建InputField组件&#xff1a;右键->UI->Input Field -TextMeshPro。 2、把Input Field Settings中的Line Type设置为Multi Line Newline模式&#x…

8.2K star!史上最强Web应用防火墙

&#x1f6a9; 0x01 介绍 长亭雷池SafeLine是长亭科技耗时近 10 年倾情打造的WAF(Web Application Firewall)&#xff0c;一款敢打出口号 “不让黑客越雷池一步” 的 WAF&#xff0c;我愿称之为史上最强的一款Web应用防火墙&#xff0c;足够简单、足够好用、足够强的免费且开源…

php laravel 二维码

public function qr($url,$name2,$inpath){require_once(dirname(__FILE__) . /../../../Library/phpqrcode/phpqrcode.php);$errorCorrectionLevel L;//容错级别$matrixPointSize 10;//生成图片大小$QRcode new \QRcode() ;$QRcode->png($url, $inpath.$name2, $errorCor…

【Spring 篇】走进Java NIO的奇妙世界:解锁高效IO操作的魔法

欢迎来到Java NIO的神奇之旅&#xff01;在这个充满活力的世界里&#xff0c;我们将一起揭示Java NIO&#xff08;New I/O&#xff09;的奥秘&#xff0c;探索其在高效IO操作中的神奇魔法。无需担心&#xff0c;即使你是Java的小白&#xff0c;也能轻松领略这个强大而灵活的IO框…

mac安装rust开发环境,使用brew安装和全局配置

mac下使用brew可以一键安装环境&#xff1a; brew install rustup 安装完成执行&#xff1a; rustup-init 按照提示配置即可&#xff1a; 出现&#xff1a; 想要全局生效&#xff1a; echo export PATH"$HOME/.cargo/bin:$PATH" >> ~/.bash_profile source…

C# xaml框架以及Java的ORM介绍

c#有ASP.Net,.NET以及EF Core这几个重要的运行时和框架.分别用于web,应用以及数据库的ORM. 目前跨平台的有Avalonia UI,.Net MAUI以及Uno Platform,至于WPF等本身不是跨平台的,但可以依靠其他库实现跨平台.这里面Avalonia应该是认为bug比较少的. 当然目前最火的跨平台解决方案…

实现elasticsearch和数据库的数据同步

1. 数据同步 elasticsearch中的酒店数据来自于mysql数据库&#xff0c;因此mysql数据发生改变时&#xff0c;elasticsearch也必须跟着改变&#xff0c;这个就是elasticsearch与mysql之间的数据同步。 1.1. 思路分析 常见的数据同步方案有三种&#xff1a; 同步调用 异步通知…

【Roadmap to Learn LLM】Intro to Large Language Models

by Andrej Karpathy 文章目录 什么是LLM模型训练微调阶段llm的发展方向LLM安全参考资料 什么是LLM Large Language Model(LLM)就是两个文件&#xff0c;一个是模型参数文件&#xff0c;一个是用于运行模型的代码文件 模型训练 一个压缩的过程&#xff0c;将所有训练数据压缩…

就业班 第二阶段 2401--3.19 day2 DDL DML DQL 多表查询

在mysql库里的语句 \G 竖着排列 ; \g 横着排列 数据库用户组成 双单引号单都行 -- sql的注释 创建mysql用户&#xff1a;&#xff08;兼容5.7 8.0 &#xff09; create user root% identified by Qwer123..; grant all on *.* to root%; flush privileges; mysql 5.7 grant …

【TB作品】MSP430单片机,音乐播放器,四首音乐,八音盒,Proteus仿真

文章目录 题目要求仿真结果实验报告&#xff1a;基于MSP430单片机的八音盒设计实验目的实验设备实验原理总结 代码和仿真图 题目要求 八音盒 本设计利用MSP430单片机结合内部定时器及LED/LCD,设计一个八音盒,按下单键可以演奏预先设置的歌曲旋律。 基本要求: 使用LED/LCD显示器…

JAVA22 FFM实战之HelloWorld

前言 JDK22即将发布&#xff0c;Java Foreign Function & Memory API将会退出预览&#xff0c;是时候开始学习一波了。 FFM API介绍 FFM API由两大部分组成&#xff0c;一个是Foreign Function Interface&#xff0c;另一个是Memory API。前者是外部函数接口&#xff0c…

2024 年广西职业院校技能大赛高职组《云计算应用》赛项赛题第 1 套

#需要资源或有问题的&#xff0c;可私博主&#xff01;&#xff01;&#xff01; #需要资源或有问题的&#xff0c;可私博主&#xff01;&#xff01;&#xff01; #需要资源或有问题的&#xff0c;可私博主&#xff01;&#xff01;&#xff01; 某企业根据自身业务需求&#…

Qt QGraphicsView移动、缩放

原链接 首先需要明白&#xff0c;view在整个视图框架中的角色是用于显示scene的&#xff0c;所以决定了如何展示scene&#xff0c;包括scale()函数&#xff0c;用于放大缩小所展示的scene&#xff1b;centerOn()函数&#xff0c;决定scene的中心在何方。所有的操作&#xff0c…

【Python + Django】启动简单的文本页面

前言&#xff1a; 为了应付&#xff08;bushi&#xff09;毕业论文&#xff0c;总要自己亲手搞一个像模像样的项目出来吧 ~ ~ 希望自己能在新的连载中学到项目搭建的知识&#xff0c;这也算是为自己的测试经历增添光彩吧&#xff01;&#xff01;&#xff01; 希望、希望大家…

uni-popup(实现自定义弹窗提示、交互)

一般提示框的样式&#xff0c;一般由设计稿而定&#xff0c;如果用uniapp的showmodel&#xff0c;那个并不能满足我们需要的自定义样式&#xff0c;所以最好的方式是我们自己封装一个&#xff01;&#xff08;想什么样就什么样&#xff09;&#xff01; 一、页面效果 二、使用…

什么是 HTTPS?它是如何解决安全性问题的?

什么是 HTTPS&#xff1f; HTTPS&#xff08;HyperText Transfer Protocol Secure&#xff09;是一种安全的通信协议&#xff0c;用于在计算机网络上安全地传输超文本&#xff08;如网页、图像、视频等&#xff09;和其他数据。它是 HTTP 协议的安全版本&#xff0c;通过使用加…