Kotlin 协程(Coroutine)
1. 协程基础概念
协程是什么?与线程的区别?
| 维度 | 线程 | 协程 |
|---|---|---|
| 调度 | 操作系统内核调度 | 用户态调度(Kotlin 由 Dispatcher 调度) |
| 切换成本 | 高(涉及内核态切换) | 低(用户态切换) |
| 数量 | 有限(几千个就占满内存) | 可创建百万级 |
| 阻塞 | 阻塞线程会影响其他任务 | 挂起(suspend)不阻塞线程 |
核心概念(看底部说明):
-
suspend:挂起函数,可在不阻塞线程的情况下暂停执行 -
CoroutineScope:协程作用域,管理协程生命周期 -
Job:协程的句柄,可控制取消 -
Dispatcher:调度器,决定协程在哪个线程执行
kotlin
// 三种标准 Dispatcher
Dispatchers.Main // Android 主线程,更新 UI
Dispatchers.IO // 适合 IO 密集型(网络、数据库、文件)
Dispatchers.Default // 适合 CPU 密集型(排序、JSON 解析、图片处理)
Dispatchers.Unconfined // 从当前线程启动,挂起后在任意线程恢复(一般不用)
2. 启动协程的方式
launch、async、runBlocking 的区别?
kotlin
// launch:启动一个新协程,返回 Job,不返回结果
val job = scope.launch {
delay(1000L)
println("World!")
}
// async:启动协程,返回 Deferred(可等待的结果)
val deferred = scope.async {
fetchData() // 耗时操作
}
val result = deferred.await() // 挂起等待结果
// runBlocking:阻塞当前线程直到协程完成(仅用于测试或桥接代码)
runBlocking {
delay(1000L)
println("Done")
}
关键区别:
-
launch:fire-and-forget,异常会立即向上传播 -
async:用于并发执行并获取结果,异常在await()时抛出;建议使用supervisorScope避免一个 async 失败影响其他
3. 协程取消与异常
协程如何取消?为什么有时取消不了?
kotlin
val job = scope.launch {
try {
repeat(1000) { i ->
println("job: I'm sleeping $i ...")
delay(500L) // 挂起函数会检查取消状态
}
} finally {
// 清理资源
withContext(NonCancellable) {
println("job: I'm running finally")
delay(1000L)
println("job: And I've just delayed for 1 sec because I'm non-cancellable")
}
}
}
delay(1300L)
job.cancelAndJoin() // 取消并等待完成
// 无法取消的情况:阻塞操作不会检查取消状态
scope.launch {
while (isActive) { // 应使用 isActive 检查状态
// 耗时计算,不会自动取消
}
}
// 解决方案:使计算可取消
while (isActive) {
// 或者使用 yield() 检查取消
yield()
}
取消传播规则:
-
父协程取消 → 所有子协程取消
-
子协程异常 → 默认取消父协程和所有兄弟协程(可通过
SupervisorJob改变)
4. 协程作用域与结构化并发
SupervisorJob 与 Job 的区别?supervisorScope 的作用?
kotlin
// Job:一个子协程失败,所有兄弟协程和父协程都取消
val scope = CoroutineScope(Job() + Dispatchers.Main)
// SupervisorJob:一个子协程失败,不影响其他子协程
val supervisorScope = CoroutineScope(SupervisorJob() + Dispatchers.Main)
// supervisorScope:用于 async 并发时隔离异常
supervisorScope {
val deferred1 = async { fetchUser() }
val deferred2 = async { fetchOrders() }
// 一个失败不影响另一个
val user = try { deferred1.await() } catch (e: Exception) { null }
val orders = try { deferred2.await() } catch (e: Exception) { null }
}
Android 中的 Scope:
kotlin
// ViewModel 中使用 viewModelScope(自动在 ViewModel 清除时取消)
class MyViewModel : ViewModel() {
fun loadData() {
viewModelScope.launch {
val data = repository.fetchData()
_uiState.value = data
}
}
}
// LifecycleOwner 中使用 lifecycleScope
class MyActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
lifecycleScope.launch {
// 自动在 Activity 销毁时取消
}
// 特定生命周期状态启动
lifecycleScope.launchWhenStarted {
// 仅在 STARTED 及以上状态执行
}
}
}
5. Flow(响应式流)
Flow、LiveData、RxJava 的区别?冷流与热流?
kotlin
// Flow:冷流,每次 collect 都会重新执行构建代码
fun fetchData(): Flow<String> = flow {
for (i in 1..3) {
delay(100)
emit("item $i") // 发射数据
}
}.flowOn(Dispatchers.IO) // 指定上游执行线程
// 使用
lifecycleScope.launch {
fetchData()
.filter { it.contains("1") }
.map { it.uppercase() }
.catch { e -> emit("Error: ${e.message}") } // 异常处理
.collect { value -> // 终端操作,触发执行
println(value)
}
}
// StateFlow:热流,有状态,新订阅者立即获得当前值
private val _state = MutableStateFlow(UiState())
val state: StateFlow<UiState> = _state.asStateFlow()
// SharedFlow:热流,可配置 replay、buffer
private val _events = MutableSharedFlow<Event>(replay = 0)
val events: SharedFlow<Event> = _events.asSharedFlow()
Flow vs LiveData:
| 特性 | Flow | LiveData |
|---|---|---|
| 生命周期感知 | 需配合 repeatOnLifecycle |
内置 |
| 线程 | 可在任意线程发射/收集 | 必须在主线程 |
| 组合操作 | 丰富的操作符 | 有限 |
| 背压 | 支持多种策略 | 不支持 |
kotlin
// Flow 生命周期安全收集
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.uiState.collect { state ->
updateUi(state)
}
}
}
除了你提到的四个,Kotlin 协程的核心概念还包括:
上下文与组合
CoroutineContext(协程上下文)
-
协程运行环境的"容器",包含 Dispatcher、Job、CoroutineName、CoroutineExceptionHandler 等元素的集合
-
通过
+运算符组合:Dispatchers.IO + Job() + CoroutineName("myJob")
Continuation(续体)
-
挂起函数编译后的底层表示,保存了挂起点的状态与恢复逻辑
-
suspend的本质就是Continuation<T>.(T) -> Unit的 CPS 变换
启动与并发
async / await
-
async启动一个协程并返回Deferred<T>(带结果的 Job) -
await()非阻塞地等待结果,是协程并发组合的关键
launch
- 启动"即发即忘"的协程,返回 Job,不携带结果值
runBlocking
-
在普通阻塞代码与协程世界之间搭桥的函数,会阻塞当前线程直到内部协程完成
-
主要用于
main函数和单元测试,生产代码中应避免
withContext
-
在指定调度器中执行代码块并返回结果,执行完后自动切回原上下文
-
常用于 IO 操作:
withContext(Dispatchers.IO) { ... }
生命周期与异常
SupervisorJob / supervisorScope
- 监督作用域:子协程失败不会取消兄弟协程,适用于"一个模块挂了不影响其他模块"的场景
CoroutineExceptionHandler
- 捕获未处理异常的处理器,必须安装在 CoroutineScope 的上下文中
CancellationException
- 协程取消时抛出的特殊异常,通常不需要捕获处理
NonCancellable
- 一种特殊的 CoroutineContext,让代码块在协程取消时仍能执行完清理逻辑
数据流与通信
Flow(冷流)
-
类似 RxJava 的响应式流,按需生产数据,使用
collect终端操作触发执行 -
操作符:
map、filter、flatMapMerge、catch、onCompletion等
SharedFlow / StateFlow(热流)
-
SharedFlow:多播事件流,有缓存策略 -
StateFlow:带最新值的 SharedFlow,是LiveData的协程替代,支持状态管理
Channel
-
协程间的通信原语,类似阻塞队列但完全非阻塞
-
类型:
Channel.UNLIMITED、Channel.CONFLATED、Channel.BUFFERED
select
- 多路复用,同时等待多个挂起操作(多个 Channel 接收、多个 Deferred await),哪个先就绪就执行哪个
同步原语
Mutex
- 协程级别的互斥锁,
withLock { }挂起而非阻塞线程
Semaphore
- 信号量,控制并发访问数量
其他重要概念
yield()
- 挂起函数,主动让出当前协程的执行权,让同调度器上的其他协程有机会运行
delay()
- 非阻塞延迟,与
Thread.sleep()不同,不会占用线程资源
CoroutineStart
- 启动模式:
DEFAULT(立即调度)、LAZY(惰性启动,需调用 start/await)、ATOMIC(取消前必须开始)、UNDISPATCHED(先在当前线程执行到第一个挂起点)
suspendCancellableCoroutine / suspendCoroutine
- 将回调式 API(如 Android 的
Retrofit回调)桥接为挂起函数的底层工具
一句话总结 :你列出的四个是骨架,而 CoroutineContext (环境配置)、Flow/Channel (数据传递)、async/await (并发组合)、SupervisorJob/ExceptionHandler (容错治理)和 Continuation(底层机制)共同构成了协程的完整生态。