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 进阶开发的关键一步,它让你能够以直观的顺序代码处理复杂的异步场景,大幅提升应用性能和可维护性。
