协程基础:概念与优势
1. 什么是协程?
协程,说白了就是一段可以被挂起、稍后又能从挂起点继续执行的代码块。它不是线程,而是运行在线程上的“轻量级任务”。我刚开始接触 Kotlin 协程时,经常把它和线程搞混,觉得不就是并发的另一种写法吗?后来才意识到,它最核心的价值不在于“并发”,而在于“挂起不阻塞线程”。
举个例子:你发出一个网络请求,线程等着响应。传统写法你可能会用回调或者阻塞式 IO。协程的做法是——挂起当前协程,释放线程去干别的活,等数据回来再自动恢复执行。线程没闲着,协程也没停,只是两者解耦了。
2. 挂起与恢复
挂起函数(suspend 关键字标记)是协程的基础。编译器会把挂起函数改造成状态机,每次挂起就是一个状态点。说白了,Kotlin 在背后帮你维护着一个 Continuation 对象,记录着执行到哪一步、局部变量是什么。
suspend fun fetchData(): String {
val result = withContext(Dispatchers.IO) {
// 模拟网络请求,挂起但不阻塞线程
delay(1000)
"data from server"
}
return result
}
你想想看,delay() 不是 Thread.sleep()。后者让当前线程睡 1 秒,线程被占着不能干别的;前者挂起协程,线程马上回去做其他协程的活。这就是“轻量”的根本原因。
我曾经在线上环境排查过一个 ANR,就是因为有人在 Dispatchers.Main 上用了 Thread.sleep() 来模拟延迟——活生生把 UI 线程卡住了。换成协程的 delay() 后,问题立刻消失。
delay() 是编译不过的。
3. 结构化并发
这是 Kotlin 协程最让我喜欢的设计。简单说就是:协程有父子层级,父协程取消,所有子协程自动取消;父协程等待所有子协程完成后才会结束。这完美避免了手动管理生命周期带来的泄漏风险。
fun main() = runBlocking {
val parentJob = launch {
launch {
delay(2000)
println("child 1 done")
}
launch {
delay(1000)
println("child 2 done")
}
}
delay(500)
parentJob.cancel() // 两个子协程都会被取消
}
我习惯把 CoroutineScope 看作一个“容器”,里面装着所有协程。在 Android 里,Activity 或 Fragment 销毁时取消对应的 viewModelScope 或 lifecycleScope,协程就自动清理了。可惜很多人没用好,导致协程泄漏——这就是咱们这个课程要解决的核心问题。
4. 协程的优势
| 特性 | 线程 | 协程 |
|---|---|---|
| 创建开销 | ~1 MB 内核栈 | 几十字节堆内存 |
| 上下文切换 | 内核调度,耗时 | 用户态挂起,纳秒级 |
| 可取消性 | 需要 Interrupt 机制,麻烦 | 内置 cancellation 协程体 |
| 结构化生命周期 | 无 | 通过 Scope 自动管理 |
| 与回调的互操作性 | 需手动包装 | suspendCancellableCoroutine 优雅转换 |
你看这个表,协程在几乎所有维度都占优。但它不是银弹——比如 CPU 密集型计算,协程挂起并不能并行,还是得靠多线程。不过对于 IO 密集型任务(网络请求、数据库读写),协程可以说是最佳选择。
5. 使用场景与注意事项
实际项目中,协程用得最频的地方就是网络请求。我们用 Retrofit + 协程,suspend fun 直接返回结果,不用层层回调。但这里有个陷阱:
为了避免这种情况,lifecycleScope 自动帮我们管理了生命周期——页面 onDestroy 时,作用域自动取消。但如果你自己创建了 CoroutineScope 却忘了取消,或者用了 GlobalScope,麻烦就来了。
我遇到过一位同事,在 RecyclerView 的 ViewHolder 里用 GlobalScope.launch 发起网络请求,结果列表快速滑动时,创建了几十个协程同时跑,响应回来时 View 已经回收了,直接导致 IllegalStateException。从那以后,我要求团队:所有协程必须绑定生命周期,严禁使用 GlobalScope。
withContext 配合 NonCancellable。我们后面章节会详细讲这个案例。
这张图展示了协程在 CoroutineScope 内的生命周期:父协程启动两个子协程,子协程可以挂起(如网络 IO),随后恢复完成。如果父协程被取消,或作用域结束,所有子协程都会被取消——这正是避免协程泄漏的关键机制。