Kotlin: Kotlin协程Coroutines详解
最后更新:2026-08-26
协程是 Kotlin 异步编程的核心—— 函数让 Charlie 用同步写法处理异步操作,结构化并发确保没有泄漏的协程,这是竞品教程完全缺失的内容。
1. 你将学到
- 协程 vs 线程:轻量级、非阻塞、结构化并发
- 函数:异步世界的"同步写法"
- / / :结构化并发三要素
- (发射后不管)vs (等待结果)
- Charlie 实战:
2. 一个架构师的真实故事
(1) 痛点:回调地狱与线程爆炸
Charlie 的 Java 版 OrderProcessor 用 编写异步逻辑,3 层嵌套回调已经不可读。团队用 200 个线程处理并发请求,CPU 上下文切换开销占 40%。
(2) 协程的解法
KOTLIN
// Java: nested CompletableFuture callbacks
CompletableFuture`<Order>` future = fetchOrder(id)
.thenCompose(order -> fetchCustomer(order.getCustomerId()))
.thenApply(customer -> enrichOrder(order, customer))
.exceptionally(ex -> handleError(ex));
// Kotlin: sequential-looking code, async execution
suspend fun processOrder(id: String): Order {
val order = fetchOrder(id) // Suspend, not block
val customer = fetchCustomer(order.customerId) // Suspend again
return enrichOrder(order, customer)
}
函数让异步代码读起来像同步——协程在等待时挂起而非阻塞线程,1 个线程可以处理 10 万个协程。
3. 协程 vs 线程
(1) 核心差异
KOTLIN
// Thread: 1 thread per concurrent task (heavy)
// 100,000 threads = OOM (each thread ~1MB stack)
// Coroutine: lightweight virtual threads
// 100,000 coroutines = fine (each coroutine ~few hundred bytes)
fun main() = runBlocking {
repeat(100_000) {
launch { // 100K coroutines - no problem!
delay(1_000) // Suspend (not block)
println("Coroutine $it done")
}
}
}
(2) 协程 vs 线程对比
| 维度 | 线程 | 协程 |
|---|---|---|
| 创建成本 | ~1MB 栈内存 | ~几百字节 |
| 上下文切换 | OS 内核级(慢) | 用户态(快) |
| 数量上限 | 几千 | 几十万 |
| 阻塞 | 阻塞整个线程 | 只挂起协程 |
| 取消 | 不安全(stop 废弃) | 协作式取消(安全) |
| 异常 | 难以传播 | 结构化传播 |
(3) 协程原理示意
sequenceDiagram
participant T as Thread
participant C1 as Coroutine 1
participant C2 as Coroutine 2
participant IO as IO Operation
T->>C1: Resume
C1->>IO: fetchOrder(id)
Note over C1: SUSPEND - thread is FREE
T->>C2: Resume (same thread!)
C2->>IO: fetchCustomer(id)
Note over C2: SUSPEND - thread is FREE
IO-->>C1: Order result
Note over C1: RESUME
C1-->>T: Continue processing
IO-->>C2: Customer result
Note over C2: RESUME
4. suspend 函数
(1) 基本概念
KOTLIN
// suspend keyword: this function can suspend the coroutine
suspend fun fetchOrder(id: String): Order {
delay(500) // Simulate network call (non-blocking)
return Order(id, 299.99, "CONFIRMED")
}
// suspend functions can only be called from coroutines or other suspend functions
suspend fun processOrder(id: String): Order {
val order = fetchOrder(id) // Suspend point
val customer = fetchCustomer(order.customerId) // Suspend point
return order.copy(customer = customer)
}
(2) suspend 函数规则
| 规则 | 说明 |
|---|---|
| 只能从协程或 suspend 函数调用 | 编译器强制 |
| 不阻塞线程 | 挂起协程,释放线程 |
| 可调用普通函数 | 普通函数不能调用 suspend |
| 本质是 CPS 变换 | 编译器将 suspend 转为状态机 |
5. 结构化并发三要素
(1) CoroutineScope
KOTLIN
// CoroutineScope: defines the lifetime of coroutines
// All coroutines launched in a scope are bound to its lifetime
// runBlocking: blocks the current thread until all coroutines complete
runBlocking {
launch { delay(1_000); println("Done") }
}
// coroutineScope: suspends (not blocks) until all children complete
suspend fun fetchAll() = coroutineScope {
val order = async { fetchOrder("ORD-001") }
val customer = async { fetchCustomer("CUST-001") }
Pair(order.await(), customer.await())
}
// Custom scope (e.g., in a class)
class OrderService {
private val scope = CoroutineScope(Dispatchers.Default + SupervisorJob())
fun process(order: Order) {
scope.launch { /* async work */ }
}
fun shutdown() {
scope.cancel() // Cancel all child coroutines
}
}
(2) Job — 协程的生命周期
KOTLIN
val job = launch {
println("Working...")
delay(1_000)
println("Done")
}
// Job states: New -> Active -> Completing -> Completed
// -> Cancelling -> Cancelled
job.cancel() // Request cancellation
job.join() // Wait for completion
job.cancelAndJoin() // Cancel + wait
(3) Dispatcher — 线程调度器
KOTLIN
// Dispatchers.Default: CPU-intensive work (parallelism = CPU cores)
launch(Dispatchers.Default) { computeOrderTax() }
// Dispatchers.IO: blocking I/O operations (up to 64 threads)
launch(Dispatchers.IO) { fetchDataFromDb() }
// Dispatchers.Main: UI thread (Android/Swing)
launch(Dispatchers.Main) { updateUI() }
// Custom dispatcher
val orderDispatcher = Executors.newFixedThreadPool(8).asCoroutineDispatcher()
launch(orderDispatcher) { processOrder() }
(4) Dispatcher 对比
| Dispatcher | 线程数 | 适用场景 | 典型操作 |
|---|---|---|---|
| CPU 核数 | CPU 密集 | 排序、计算 | |
| 最多 64 | 阻塞 I/O | 网络、数据库 | |
| 1 | UI 更新 | Android/桌面 | |
| 自定义 | 自定义 | 特定需求 | 隔离线程池 |
6. launch vs async
(1) launch — 发射后不管
KOTLIN
// launch: fire-and-forget (returns Job, not result)
val job: Job = launch {
delay(1_000)
println("Background work done")
}
job.join() // Wait for completion
(2) async — 等待结果
KOTLIN
// async: returns Deferred`<T>` (a future-like Job)
val deferred: Deferred`<Order>` = async {
fetchOrder("ORD-001")
}
val order = deferred.await() // Suspend until result is ready
(3) 并发组合
KOTLIN
suspend fun processOrderConcurrently(id: String): EnrichedOrder = coroutineScope {
// Launch in parallel within coroutineScope
val orderDeferred = async { fetchOrder(id) }
val customerDeferred = async { fetchCustomer(id) }
val inventoryDeferred = async { checkInventory(id) }
// Await all results
val order = orderDeferred.await()
val customer = customerDeferred.await()
val inventory = inventoryDeferred.await()
EnrichedOrder(order, customer, inventory)
}
(4) launch vs async 对比
| 维度 | ||
|---|---|---|
| 返回值 | ||
| 获取结果 | 无 | |
| 异常处理 | 传播到父协程 | 存储在 Deferred 中 |
| 适用场景 | 副作用(日志、通知) | 需要返回值 |
| 类比 |
7. 协程异常处理
KOTLIN
// try-catch in coroutine
launch {
try {
fetchOrder(id)
} catch (e: Exception) {
logger.error("Failed to fetch order", e)
}
}
// CoroutineExceptionHandler
val handler = CoroutineExceptionHandler { _, exception ->
logger.error("Coroutine error", exception)
}
launch(handler) {
fetchOrder(id) // Uncaught exception handled by handler
}
// SupervisorJob: child failure doesn't cancel siblings
coroutineScope {
val supervisor = SupervisorJob()
with(supervisor) {
launch { throw Exception("Child 1 fails") } // Only this child fails
launch { delay(100); println("Child 2 still runs") } // Sibling survives
}
}
8. 完整示例:OrderProcessor 异步处理
KOTLIN
// ============================================
// OrderProcessor - Async Processing with Coroutines
// Feature: Concurrent order enrichment
// ============================================
import kotlinx.coroutines.*
data class Order(val id: String, val total: Double, val customerId: String, var customerName: String? = null)
data class Customer(val id: String, val name: String, val tier: String)
// Simulate async operations
suspend fun fetchOrder(id: String): Order {
delay(100) // Simulate DB query
return Order(id, 299.99 + id.substring(4).toInt() * 100, "CUST-${id.substring(4)}")
}
suspend fun fetchCustomer(id: String): Customer {
delay(150) // Simulate API call
return Customer(id, "Customer-$id", if (id.endsWith("1")) "VIP" else "STANDARD")
}
suspend fun checkInventory(orderId: String): Boolean {
delay(80) // Simulate inventory check
return true
}
// Sequential processing
suspend fun processSequential(id: String): Order {
val order = fetchOrder(id) // 100ms
val customer = fetchCustomer(order.customerId) // 150ms
val available = checkInventory(id) // 80ms
// Total: ~330ms
return order.copy(customerName = customer.name)
}
// Concurrent processing
suspend fun processConcurrent(id: String): Order = coroutineScope {
val orderDeferred = async { fetchOrder(id) }
val order = orderDeferred.await()
// Fetch customer and inventory in parallel
val customerDeferred = async { fetchCustomer(order.customerId) }
val inventoryDeferred = async { checkInventory(id) }
val customer = customerDeferred.await()
val available = inventoryDeferred.await()
// Total: ~100ms + ~150ms = ~250ms (customer and inventory parallel)
if (!available) throw RuntimeException("Inventory unavailable for $id")
order.copy(customerName = "${customer.name} (${customer.tier})")
}
// Batch processing
suspend fun processBatch(orderIds: List`<String>`): List`<Order>` = coroutineScope {
orderIds.map { id ->
async { processConcurrent(id) }
}.awaitAll()
}
fun main() = runBlocking {
val orderIds = listOf("ORD-001", "ORD-002", "ORD-003", "ORD-004", "ORD-005")
// Measure sequential
val seqStart = System.currentTimeMillis()
val seqResults = orderIds.map { processSequential(it) }
val seqTime = System.currentTimeMillis() - seqStart
println("Sequential: ${seqTime}ms for ${seqResults.size} orders")
// Measure concurrent
val conStart = System.currentTimeMillis()
val conResults = processBatch(orderIds)
val conTime = System.currentTimeMillis() - conStart
println("Concurrent: ${conTime}ms for ${conResults.size} orders")
// Print results
println("\n=== Processed Orders ===")
conResults.forEach { order ->
println(" ${order.id}: \$${order.total} USD | ${order.customerName}")
}
// Structured concurrency: error in one cancels all
println("\n=== Error Handling ===")
try {
coroutineScope {
launch { delay(200); println("Task 1 done") }
launch { delay(100); throw RuntimeException("Task 2 failed!") }
launch { delay(300); println("Task 3 done") }
}
} catch (e: RuntimeException) {
println("Caught: ${e.message}")
}
}
输出:
TEXT
📖 仅展示
Sequential: 1650ms for 5 orders
Concurrent: 370ms for 5 orders
=== Processed Orders ===
ORD-001: $1100 USD | Customer-CUST-001 (VIP)
ORD-002: $1200 USD | Customer-CUST-002 (STANDARD)
ORD-003: $1300 USD | Customer-CUST-003 (STANDARD)
ORD-004: $1400 USD | Customer-CUST-004 (STANDARD)
ORD-005: $1500 USD | Customer-CUST-005 (STANDARD)
=== Error Handling ===
Task 1 done
Caught: Task 2 failed!
❓ 常见问题
Q 协程和虚拟线程(Virtual Threads)有什么区别?
A 协程是 Kotlin 的用户态实现,需要 标记;虚拟线程是 JVM 的内核级实现(JDK 21+),无需修改代码。协程更灵活(多平台),虚拟线程更透明(无需改库)。
Q delay() 和 Thread.sleep() 有什么区别?
A 挂起协程释放线程, 阻塞整个线程。在协程中永远用 ,不用 。
Q runBlocking 什么时候用?
A 主要用于 函数和测试中,作为协程的入口点。生产代码中应避免使用——它会阻塞当前线程。
Q 协程取消后如何清理资源?
A 用 或 函数。协程取消时执行 finally 块,确保资源释放。
Q async 的异常什么时候抛出?
A 的异常存储在 中,调用 时才抛出。如果从不 await,异常会丢失。
Q 什么时候用 SupervisorJob?
A 当子协程的失败不应影响其他子协程时。例如 UI 中一个请求失败不应取消其他请求。普通 中一个子失败会取消所有兄弟。
📖 小节
- 协程是轻量级线程,创建成本极低(几百字节 vs 1MB)
- 函数让异步代码读起来像同步——挂起而非阻塞
- 结构化并发: 确保所有子协程完成或取消
- 发射后不管, 等待返回值
- 控制协程在哪个线程池执行:Default/IO/Main
- 异常处理: / /
📝 作业
- 基础题(难度⭐):用 + 启动 3 个协程,分别 delay 不同时间后打印消息。提示:
- 进阶题(难度⭐⭐):用 + 并发获取订单和客户信息,组合成完整订单。提示:
- 挑战题(难度⭐⭐⭐):实现一个带超时和重试的订单获取函数:超时 3 秒、最多重试 3 次、指数退避。提示: + 循环