协程¶
协程是 Kotlin 处理并发和异步编程的核心方案。它让你用接近顺序代码的方式编写异步逻辑,同时避免为大量并发任务创建大量操作系统线程。
对 Java 开发者来说,可以先把协程理解为“可挂起的计算”:它不是线程,但会在线程上运行;它可以暂停并释放线程,稍后再恢复执行。
为什么不是直接用线程¶
Java 线程由操作系统管理,功能强,但成本较高。大量线程会带来:
- 栈内存占用。
- 上下文切换成本。
- 调度压力。
- 难以管理的生命周期。
协程更轻量。协程挂起时不会阻塞线程,线程可以继续执行其他协程。因此协程适合大量 IO 等待、细粒度并发和 UI/服务端异步流程。
依赖¶
语言本身提供 suspend,但大多数协程能力来自 kotlinx.coroutines。
Gradle Kotlin DSL:
repositories {
mavenCentral()
}
dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.11.0")
}
版本应按项目 Kotlin 版本和官方发布说明更新。
suspend 函数¶
suspend fun loadUser(id: Long): User {
delay(100)
return User(id, "Ada")
}
suspend 表示函数可以挂起和恢复。挂起不是阻塞线程。
只能从下面位置调用 suspend 函数:
- 另一个 suspend 函数。
- 协程内部。
- 特殊桥接函数,例如
runBlocking。
示例:
suspend fun main() {
val user = loadUser(1)
println(user)
}
runBlocking¶
runBlocking 会创建协程作用域,并阻塞当前线程直到内部协程完成:
fun main() = runBlocking {
val user = loadUser(1)
println(user)
}
它适合:
main函数桥接。- 测试代码。
- 无法修改为 suspend 的边界。
不适合:
- Web 请求处理线程中随意包裹异步代码。
- Android 主线程。
- 已经在协程中的代码。
如果你在协程里还想切换上下文,使用 withContext。
CoroutineScope 与结构化并发¶
协程必须运行在 CoroutineScope 中。结构化并发的核心思想是:相关协程形成父子层级,父协程会等待子协程完成;父协程取消时,子协程也会取消。
suspend fun loadDashboard(): Dashboard = coroutineScope {
val user = async { loadUser() }
val orders = async { loadOrders() }
Dashboard(user.await(), orders.await())
}
coroutineScope 会等待内部所有子协程结束才返回。
不要随意创建脱离生命周期的全局协程。Java 中常见“随手 new Thread/start”的问题,在协程里对应“随手 GlobalScope.launch”。
launch 与 async¶
launch 启动一个不直接返回结果的协程,返回 Job:
val job = launch {
sendLog()
}
job.join()
适合 fire-and-wait 或后台任务。
async 启动一个会产生结果的协程,返回 Deferred<T>:
val user = async { loadUser() }
val orders = async { loadOrders() }
Dashboard(user.await(), orders.await())
适合并发计算结果。不要用 async 后不 await(),那通常说明你应该用 launch。
默认顺序执行¶
suspend 函数默认仍然顺序执行:
val user = loadUser()
val orders = loadOrders()
这不会自动并发。需要并发时显式使用 async:
val user = async { loadUser() }
val orders = async { loadOrders() }
Dashboard(user.await(), orders.await())
这种显式性很重要:并发会影响异常传播、取消和资源占用,不应该悄悄发生。
Dispatcher¶
Dispatcher 决定协程在哪些线程上执行。
常见 dispatcher:
Dispatchers.Default:CPU 密集型任务,例如计算、解析、排序。Dispatchers.IO:阻塞 IO,例如文件、数据库、旧 HTTP 客户端。Dispatchers.Main:UI 主线程,Android、桌面 UI 等场景。
切换上下文:
suspend fun readConfig(): Config =
withContext(Dispatchers.IO) {
file.readText().toConfig()
}
不要把阻塞 IO 放到 Default,也不要在 Main 上执行耗时操作。
取消¶
协程取消是协作式的。常见挂起函数会检查取消并抛出 CancellationException。
val job = launch {
repeat(1000) {
delay(100)
println(it)
}
}
delay(500)
job.cancel()
CPU 密集循环需要主动检查:
while (isActive) {
doChunk()
}
不要吞掉 CancellationException,否则取消会失效。
异常传播¶
结构化并发中,子协程失败通常会取消父作用域和兄弟协程。launch 中未捕获异常会作为协程异常处理;async 的异常通常在 await() 时暴露。
val deferred = async {
error("failed")
}
try {
deferred.await()
} catch (e: IllegalStateException) {
println(e.message)
}
异常处理要结合作用域设计,不要只在最内层 try-catch 后吞掉异常。
Flow:异步值流¶
suspend 函数返回一个值;Flow 表达一段时间内产生多个值。
fun loadPages(): Flow<String> = flow {
emit("Page 1")
emit("Page 2")
emit("Page 3")
}
suspend fun main() {
loadPages()
.map { it.uppercase() }
.collect { println(it) }
}
冷 Flow 默认惰性执行:只有 collect() 时才开始生产值。每个 collector 通常会触发一次新的执行。
冷 Flow 与热 Flow¶
冷 Flow:
- 类似异步版 Sequence。
- collect 时才执行。
- 每个 collector 独立执行。
- 适合网络请求、数据库查询、分页加载。
热 Flow:
- 即使没有 collector 也可能继续发射。
- 多个 collector 共享同一数据源。
SharedFlow适合事件广播。StateFlow适合状态持有,例如 UI state。
private val _state = MutableStateFlow(UserState.Loading)
val state: StateFlow<UserState> = _state.asStateFlow()
Channel 简述¶
Channel 用于协程之间发送和接收值,每个值通常被一个接收者消费:
val channel = Channel<Int>()
launch {
channel.send(1)
channel.close()
}
for (value in channel) {
println(value)
}
如果你想表达持续状态,优先考虑 StateFlow;如果想表达广播事件,考虑 SharedFlow;如果是生产者消费者队列,Channel 更自然。
Java 对比¶
Java 常见异步模型:
ThreadExecutorServiceFutureCompletableFuture- Reactive Streams / Reactor / RxJava
Kotlin 协程与它们的核心差异:
- 用
suspend保持顺序代码风格。 - 用结构化并发管理生命周期。
- 用
Deferred表达异步结果。 - 用 Flow 表达异步流。
- 取消和异常传播是设计的一部分。
从 Java 迁移时,不要把每个 CompletableFuture 机械翻译成 async。先看调用关系:如果只是顺序等待,suspend 函数就够;只有独立任务需要并发时才用 async。
实践建议¶
- suspend 函数默认顺序执行;并发要显式写
async或launch。 - 遵守结构化并发,避免
GlobalScope。 - 阻塞 IO 放到
Dispatchers.IO。 - CPU 密集任务放到
Dispatchers.Default。 - 不要在协程中吞掉
CancellationException。 - 返回单个异步结果用 suspend 函数,返回多个异步值用 Flow。
- Android、服务端框架和测试都有自己的协程作用域,优先使用框架提供的 scope。
参考¶
- 官方协程概览:https://kotlinlang.org/docs/coroutines-overview.html
- 官方协程基础:https://kotlinlang.org/docs/coroutines-basics.html
- 官方挂起函数组合:https://kotlinlang.org/docs/composing-suspending-functions.html
- 官方 Flow 文档:https://kotlinlang.org/docs/coroutines-flow.html