Kotlin: Kotlin协程Coroutines详解

最后更新:2026-08-26

协程是 Kotlin 异步编程的核心—— 函数让 Charlie 用同步写法处理异步操作,结构化并发确保没有泄漏的协程,这是竞品教程完全缺失的内容。

1. 你将学到


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) 协程原理示意

100%
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 中一个请求失败不应取消其他请求。普通 中一个子失败会取消所有兄弟。

📖 小节


📝 作业

  1. 基础题(难度⭐):用 + 启动 3 个协程,分别 delay 不同时间后打印消息。提示:
  2. 进阶题(难度⭐⭐):用 + 并发获取订单和客户信息,组合成完整订单。提示:
  3. 挑战题(难度⭐⭐⭐):实现一个带超时和重试的订单获取函数:超时 3 秒、最多重试 3 次、指数退避。提示: + 循环

← 上一课 | 下一课 →

Web-Tutorial.com

Web-Tutorial 技术团队

由多位开发者共同维护的编程教程平台。每篇教程由对应领域的开发者编写和审核,确保内容准确可靠。如发现任何问题,欢迎向我们反馈。

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏