11-Kotlin高阶特性-协程
Kotlin 协程(Coroutines)全面解析
协程是 Kotlin 提供的轻量级并发编程框架,它允许你以顺序的方式编写异步代码,从而避免回调地狱,并大幅简化并发任务的管理。协程不是线程,但可以运行在线程之上,通过挂起(suspend)机制实现高效的并发:挂起时释放底层线程,恢复后续续执行,整个过程非阻塞且开销极小。
一、协程核心概念
1. 什么是协程?
协程是轻量级的线程,由 Kotlin 运行时管理,而非操作系统。与线程相比:
| 特性 | 线程 | 协程 |
|---|---|---|
| 资源消耗 | 较重(每个线程占用 MB 级内存) | 极轻(可启动数十万个) |
| 切换开销 | 操作系统上下文切换 | 用户态挂起/恢复,开销极小 |
| 并发模型 | 抢占式 | 协作式(通过挂起点主动让出) |
2. 核心术语
| 术语 | 含义 |
|---|---|
suspend |
标记「可挂起函数」,仅能在协程或其他挂起函数中调用 |
CoroutineScope |
协程作用域,管理协程生命周期(如取消协程、控制协程范围) |
Dispatcher |
协程调度器,指定协程运行的线程(如 Dispatchers.Main/IO/Default) |
Job |
协程的句柄,可取消协程、监听协程状态(完成/取消/异常) |
Deferred |
带返回值的 Job,通过 await() 获取协程执行结果 |
CoroutineContext |
协程上下文,包含调度器、Job、异常处理器等信息 |
3. 核心概念关系图
flowchart TD
A[CoroutineScope<br/>协程作用域] --> B[CoroutineContext<br/>协程上下文]
B --> C[Job<br/>任务句柄]
B --> D[CoroutineDispatcher<br/>调度器]
B --> E[CoroutineExceptionHandler<br/>异常处理器]
F[launch<br/>启动协程] --> G[返回 Job<br/>无返回值]
H[async<br/>启动协程] --> I[返回 Deferred<T><br/>带返回值]
J[suspend 挂起函数] --> K[可调用其他挂起函数]
K --> L[delay/yield/withContext]
4. 挂起函数(Suspending Function)
挂起函数是协程的核心抽象,用 suspend 关键字标记,可以在协程中暂停执行而不阻塞线程,并在稍后恢复。
suspend fun fetchUserData(): String {
// 模拟网络请求(非阻塞)
delay(1000L) // delay 是一个挂起函数
return "User data"
}
关键特性:
- 只能在协程或其他挂起函数中调用;
- 挂起时释放底层线程,线程可执行其他协程;
- 恢复后续续执行,仿佛从未暂停。
5. 依赖引入
使用协程前,需要在项目中添加 kotlinx-coroutines-core 依赖(以 Gradle Kotlin DSL 为例):
dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.0")
// Android 平台还需添加 android 模块
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.8.0")
// JVM 平台可选(如 jdk8 模块)
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-jdk8:1.8.0")
}
二、协程基础用法
1. 第一个协程程序
import kotlinx.coroutines.*
fun main() = runBlocking { // 启动主协程
launch { // 启动新协程
delay(1000L) // 非阻塞等待
println("World!")
}
println("Hello,") // 主协程继续执行
delay(2000L) // 等待协程完成
}
// 输出:
// Hello,
// World!
runBlocking:创建协程作用域并阻塞当前线程,仅推荐在 main 函数/测试中使用;delay():挂起函数,暂停协程但不阻塞线程。
2. 协程构建器
| 构建器 | 作用 | 返回值 |
|---|---|---|
launch |
启动新协程,不返回结果 | Job |
async |
启动新协程,返回一个可等待的结果 | Deferred<T> |
runBlocking |
阻塞当前线程直到协程完成 | T |
(1)launch:无返回值的协程
fun main() = runBlocking {
println("主线程:${Thread.currentThread().name}")
val job1 = launch {
println("协程1:${Thread.currentThread().name}")
delay(1000)
println("协程1执行完成")
}
// 指定调度器:Dispatchers.Default(后台计算线程)
val job2 = launch(Dispatchers.Default) {
println("协程2:${Thread.currentThread().name}")
delay(500)
println("协程2执行完成")
}
job1.join()
job2.join()
println("所有协程执行完成")
}
(2)async:带返回值的协程
fun main() = runBlocking {
val deferred1 = async { calculateSum(1, 100) }
val deferred2 = async { calculateSum(101, 200) }
val result1 = deferred1.await()
val result2 = deferred2.await()
println("总和:${result1 + result2}") // 20100
}
suspend fun calculateSum(start: Int, end: Int): Int {
delay(500)
var sum = 0
for (i in start..end) sum += i
return sum
}
(3)runBlocking:阻塞式协程(仅用于桥接)
runBlocking 会阻塞当前线程,禁止在生产代码中使用(如 Android 主线程)。
3. 挂起函数(suspend)的调用规则
suspend fun fetchData(url: String): String {
delay(1000)
return "Data from $url"
}
fun main() = runBlocking {
// 在协程中调用挂起函数
launch(Dispatchers.IO) {
val data = fetchData("https://example.com")
println(data)
}
}
三、协程调度器(Dispatcher)
调度器决定协程运行的线程,核心类型:
| 调度器 | 适用场景 | 线程特性 |
|---|---|---|
Dispatchers.Main |
Android 主线程/UI 线程 | 单线程(仅 Android 可用) |
Dispatchers.IO |
网络/文件 IO、数据库操作 | 线程池(按需创建,最多 64 个) |
Dispatchers.Default |
CPU 密集型计算(如排序、解析) | 线程池(核心数 = CPU 核心数) |
Dispatchers.Unconfined |
无固定线程 | 先在当前线程执行,挂起后切换 |
newSingleThreadContext("name") |
专属单线程 | 自定义命名的单线程(需手动关闭) |
切换调度器:withContext
withContext 允许在不启动新协程的情况下切换协程上下文,常用于在 IO 线程执行耗时操作后切换回主线程。
suspend fun fetchUserData(): String = withContext(Dispatchers.IO) {
delay(1000L)
"User data"
}
fun main() = runBlocking {
val data = fetchUserData()
println(data)
}
四、协程生命周期管理
1. Job 与取消协程
launch 返回的 Job 对象可管理协程:
job.start():启动协程(默认自动启动);job.cancel():取消协程;job.join():等待协程完成;job.cancelAndJoin():取消并等待完成;job.isActive:判断协程是否活跃。
fun main() = runBlocking {
val job = launch {
repeat(10) { i ->
println("协程执行中:$i")
delay(500)
}
}
delay(1500)
println("取消协程")
job.cancelAndJoin()
println("协程已取消")
}
2. 结构化并发:CoroutineScope
CoroutineScope 用于管理多个协程的生命周期,核心规则:
- 作用域取消时,所有子协程自动取消;
- Android 中常用
lifecycleScope(生命周期绑定)、viewModelScope(ViewModel 绑定)。
fun main() {
// 创建自定义作用域
val scope = CoroutineScope(Dispatchers.IO)
scope.launch {
repeat(5) {
println("协程1:$it")
delay(500)
}
}
scope.launch {
repeat(5) {
println("协程2:$it")
delay(500)
}
}
// 等待 1 秒后取消作用域(所有子协程取消)
Thread.sleep(1000)
println("取消作用域")
scope.cancel()
}
3. 作用域构建器
| 构建器 | 类型 | 行为 |
|---|---|---|
runBlocking |
常规函数 | 阻塞当前线程直到完成 |
coroutineScope |
挂起函数 | 挂起当前协程,创建子作用域,释放线程 |
supervisorScope |
挂起函数 | 类似 coroutineScope,但子协程失败不影响兄弟协程 |
fun main() = runBlocking {
coroutineScope {
launch {
delay(1000L)
println("Task from nested scope")
}
delay(100L)
println("Task from coroutine scope")
}
println("Scope is over")
}
4. 父子 Job 的层次关系
- 父协程取消时,所有子协程自动取消;
- 子协程异常时,默认会向上传播取消父协程;
- 使用
supervisorScope可以改变异常传播行为。
五、异常处理
1. 异常的传播
- launch:异常立即抛出,可以通过
CoroutineExceptionHandler处理; - async:异常在调用
.await()时抛出,需要 try-catch 处理。
fun main() = runBlocking {
// launch 异常处理
val job = launch {
try {
throw RuntimeException("Error in launch")
} catch (e: Exception) {
println("Caught: ${e.message}")
}
}
// async 异常处理
val deferred = async {
throw RuntimeException("Error in async")
}
try {
deferred.await()
} catch (e: Exception) {
println("Caught: ${e.message}")
}
}
2. CoroutineExceptionHandler
全局异常处理器,用于捕获未处理的异常:
val handler = CoroutineExceptionHandler { _, exception ->
println("Caught: $exception")
}
fun main() = runBlocking {
val job = launch(handler) {
throw RuntimeException("Something went wrong")
}
job.join()
}
3. 监督作用域(Supervision)
普通作用域中,子协程失败会取消父协程和兄弟协程。监督作用域改变了这一行为:
fun main() = runBlocking {
supervisorScope {
val child1 = launch {
try {
delay(Long.MAX_VALUE)
} finally {
println("Child 1 cancelled")
}
}
val child2 = launch {
throw RuntimeException("Child 2 failed")
}
child2.join()
child1.cancel() // 需要手动取消
}
}
六、协程进阶用法
1. Flow:异步数据流
Flow 是协程的异步数据流,支持冷流、操作符链式调用,替代传统回调/回调流。
import kotlinx.coroutines.flow.*
fun getNumbers(): Flow<Int> = flow {
for (i in 1..5) {
delay(500)
emit(i) // 发射数据
}
}
fun main() = runBlocking {
getNumbers()
.filter { it % 2 == 0 }
.map { it * 10 }
.collect { println("接收数据:$it") }
}
// 输出:
// 接收数据:20
// 接收数据:40
| 特性 | Channel | Flow |
|---|---|---|
| 类型 | 热流(hot) | 冷流(cold) |
| 数据生产 | 独立于消费 | 随收集而生产 |
| 适用场景 | 协程间通信 | 异步数据序列 |
2. Channel:协程间的管道
Channel 允许在不同协程之间传递数据流:
import kotlinx.coroutines.channels.Channel
fun main() = runBlocking {
val channel = Channel<Int>()
launch {
for (x in 1..5) {
channel.send(x * x)
}
channel.close()
}
for (y in channel) {
println(y)
}
}
3. 合并多个异步源
- combine:合并两个 Flow
- async/await:合并多个 Deferred
fun main() = runBlocking {
// 合并两个 Flow
val flow1 = flow { emit(1); delay(100); emit(2) }
val flow2 = flow { emit("A"); delay(200); emit("B") }
combine(flow1, flow2) { num, str -> "$num-$str" }
.collect { println(it) }
// 合并两个 async 结果
val deferred1 = async { 10 }
val deferred2 = async { 20 }
println("合并结果:${deferred1.await() + deferred2.await()}")
}
4. 超时处理:withTimeout
设置协程执行超时时间:
fun main() = runBlocking {
try {
withTimeout(1000) {
repeat(3) {
println("执行中:$it")
delay(600)
}
}
} catch (e: TimeoutCancellationException) {
println("协程超时")
}
}
5. SharedFlow 与 StateFlow
用于在多个收集器之间共享状态:
- StateFlow:持有一个可观察的状态值(类似 LiveData)
- SharedFlow:可配置的事件流
七、协程的性能原理
1. 阻塞 vs 非阻塞
suspend fun blockingWork() {
Thread.sleep(1000) // 阻塞当前线程(错误用法)
}
suspend fun nonBlockingWork() {
delay(1000) // 挂起协程,释放线程
}
当使用 delay 时,协程挂起并释放线程,线程可以去执行其他协程。这是协程高效并发的基础。
2. 协程的轻量性验证
fun main() = runBlocking {
repeat(100_000) {
launch {
delay(1000L)
print(".")
}
}
}
// 内存消耗极小,运行流畅
如果用线程实现同样的逻辑,很可能会导致内存不足。
八、最佳实践
- 避免使用
GlobalScope:应使用自定义作用域或结构化并发; - 为主线程安全设计:使用
withContext(Dispatchers.IO)执行耗时操作; - 合理选择调度器:
- UI 操作:
Dispatchers.Main - CPU 密集型:
Dispatchers.Default - IO 密集型:
Dispatchers.IO
- UI 操作:
- 使用超时保护:
withTimeout防止协程无限运行; - 适当处理取消:计算密集型的协程应定期检查
isActive或调用yield(); - 使用监督作用域处理独立任务:一个子任务失败不应影响其他任务时;
- Android 开发优先使用
lifecycleScope/viewModelScope。
九、官方资料链接
- Kotlin 协程官方文档(英文):https://kotlinlang.org/docs/coroutines.html
- Kotlin 协程中文文档:https://www.kotlincn.net/docs/reference/coroutines.html
- Kotlin 协程核心指南(官方博客):https://blog.jetbrains.com/kotlin/2019/02/kotlin-1-3-coroutines/
- Flow 官方文档:https://kotlinlang.org/docs/flow.html
- Android 协程最佳实践:https://developer.android.com/kotlin/coroutines
- 通道(Channels):https://kotlinlang.org/docs/channels.html
- 异常处理:https://kotlinlang.org/docs/exception-handling.html
总结
- 核心概念:协程是轻量级并发框架,通过
suspend实现非阻塞挂起,Dispatchers灵活切换线程,以同步写法实现异步逻辑。 - 基本使用:
launch启动无返回值的协程,async返回可等待的结果,runBlocking桥接普通代码。 - 结构化并发:协程作用域管理协程生命周期,父子 Job 形成层次结构,自动传播取消和异常。
- 异常处理:
CoroutineExceptionHandler捕获未处理异常,supervisorScope改变异常传播。 - 协程通信:
Channel用于协程间通信,Flow处理异步数据流,StateFlow共享状态。 - 性能原理:挂起释放线程,非阻塞等待实现高效并发,可启动数十万协程。
掌握协程是 Kotlin 进阶开发的关键一步,它让你能够以直观的顺序代码处理复杂的异步场景,大幅提升应用性能和可维护性。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐



所有评论(0)