Kotlin 协程(Coroutines)全面解析

协程是 Kotlin 提供的轻量级并发编程框架,它允许你以顺序的方式编写异步代码,从而避免回调地狱,并大幅简化并发任务的管理。协程不是线程,但可以运行在线程之上,通过挂起(suspend)机制实现高效的并发:挂起时释放底层线程,恢复后续续执行,整个过程非阻塞且开销极小。


一、协程核心概念

1. 什么是协程?

协程是轻量级的线程,由 Kotlin 运行时管理,而非操作系统。与线程相比:

特性 线程 协程
资源消耗 较重(每个线程占用 MB 级内存) 极轻(可启动数十万个)
切换开销 操作系统上下文切换 用户态挂起/恢复,开销极小
并发模型 抢占式 协作式(通过挂起点主动让出)

2. 核心术语

术语 含义
suspend 标记「可挂起函数」,仅能在协程或其他挂起函数中调用
CoroutineScope 协程作用域,管理协程生命周期(如取消协程、控制协程范围)
Dispatcher 协程调度器,指定协程运行的线程(如 Dispatchers.Main/IO/Default
Job 协程的句柄,可取消协程、监听协程状态(完成/取消/异常)
Deferred 带返回值的 Job,通过 await() 获取协程执行结果
CoroutineContext 协程上下文,包含调度器、Job、异常处理器等信息

3. 核心概念关系图

flowchart TD
    A[CoroutineScope<br/>协程作用域] --> B[CoroutineContext<br/>协程上下文]
    
    B --> C[Job<br/>任务句柄]
    B --> D[CoroutineDispatcher<br/>调度器]
    B --> E[CoroutineExceptionHandler<br/>异常处理器]
    
    F[launch<br/>启动协程] --> G[返回 Job<br/>无返回值]
    H[async<br/>启动协程] --> I[返回 Deferred<T><br/>带返回值]
    
    J[suspend 挂起函数] --> K[可调用其他挂起函数]
    K --> L[delay/yield/withContext]

4. 挂起函数(Suspending Function)

挂起函数是协程的核心抽象,用 suspend 关键字标记,可以在协程中暂停执行而不阻塞线程,并在稍后恢复。

suspend fun fetchUserData(): String {
    // 模拟网络请求(非阻塞)
    delay(1000L) // delay 是一个挂起函数
    return "User data"
}

关键特性

  • 只能在协程或其他挂起函数中调用;
  • 挂起时释放底层线程,线程可执行其他协程;
  • 恢复后续续执行,仿佛从未暂停。

5. 依赖引入

使用协程前,需要在项目中添加 kotlinx-coroutines-core 依赖(以 Gradle Kotlin DSL 为例):

dependencies {
    implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.0")
    // Android 平台还需添加 android 模块
    implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.8.0")
    // JVM 平台可选(如 jdk8 模块)
    implementation("org.jetbrains.kotlinx:kotlinx-coroutines-jdk8:1.8.0")
}

二、协程基础用法

1. 第一个协程程序

import kotlinx.coroutines.*

fun main() = runBlocking { // 启动主协程
    launch { // 启动新协程
        delay(1000L) // 非阻塞等待
        println("World!")
    }
    println("Hello,") // 主协程继续执行
    delay(2000L) // 等待协程完成
}
// 输出:
// Hello,
// World!
  • runBlocking:创建协程作用域并阻塞当前线程,仅推荐在 main 函数/测试中使用;
  • delay():挂起函数,暂停协程但不阻塞线程。

2. 协程构建器

构建器 作用 返回值
launch 启动新协程,不返回结果 Job
async 启动新协程,返回一个可等待的结果 Deferred<T>
runBlocking 阻塞当前线程直到协程完成 T
(1)launch:无返回值的协程
fun main() = runBlocking {
    println("主线程:${Thread.currentThread().name}")

    val job1 = launch {
        println("协程1:${Thread.currentThread().name}")
        delay(1000)
        println("协程1执行完成")
    }

    // 指定调度器:Dispatchers.Default(后台计算线程)
    val job2 = launch(Dispatchers.Default) {
        println("协程2:${Thread.currentThread().name}")
        delay(500)
        println("协程2执行完成")
    }

    job1.join()
    job2.join()
    println("所有协程执行完成")
}
(2)async:带返回值的协程
fun main() = runBlocking {
    val deferred1 = async { calculateSum(1, 100) }
    val deferred2 = async { calculateSum(101, 200) }

    val result1 = deferred1.await()
    val result2 = deferred2.await()

    println("总和:${result1 + result2}") // 20100
}

suspend fun calculateSum(start: Int, end: Int): Int {
    delay(500)
    var sum = 0
    for (i in start..end) sum += i
    return sum
}
(3)runBlocking:阻塞式协程(仅用于桥接)

runBlocking 会阻塞当前线程,禁止在生产代码中使用(如 Android 主线程)。

3. 挂起函数(suspend)的调用规则

suspend fun fetchData(url: String): String {
    delay(1000)
    return "Data from $url"
}

fun main() = runBlocking {
    // 在协程中调用挂起函数
    launch(Dispatchers.IO) {
        val data = fetchData("https://example.com")
        println(data)
    }
}

三、协程调度器(Dispatcher)

调度器决定协程运行的线程,核心类型:

调度器 适用场景 线程特性
Dispatchers.Main Android 主线程/UI 线程 单线程(仅 Android 可用)
Dispatchers.IO 网络/文件 IO、数据库操作 线程池(按需创建,最多 64 个)
Dispatchers.Default CPU 密集型计算(如排序、解析) 线程池(核心数 = CPU 核心数)
Dispatchers.Unconfined 无固定线程 先在当前线程执行,挂起后切换
newSingleThreadContext("name") 专属单线程 自定义命名的单线程(需手动关闭)

切换调度器:withContext

withContext 允许在不启动新协程的情况下切换协程上下文,常用于在 IO 线程执行耗时操作后切换回主线程。

suspend fun fetchUserData(): String = withContext(Dispatchers.IO) {
    delay(1000L)
    "User data"
}

fun main() = runBlocking {
    val data = fetchUserData()
    println(data)
}

四、协程生命周期管理

1. Job 与取消协程

launch 返回的 Job 对象可管理协程:

  • job.start():启动协程(默认自动启动);
  • job.cancel():取消协程;
  • job.join():等待协程完成;
  • job.cancelAndJoin():取消并等待完成;
  • job.isActive:判断协程是否活跃。
fun main() = runBlocking {
    val job = launch {
        repeat(10) { i ->
            println("协程执行中:$i")
            delay(500)
        }
    }

    delay(1500)
    println("取消协程")
    job.cancelAndJoin()
    println("协程已取消")
}

2. 结构化并发:CoroutineScope

CoroutineScope 用于管理多个协程的生命周期,核心规则:

  • 作用域取消时,所有子协程自动取消;
  • Android 中常用 lifecycleScope(生命周期绑定)、viewModelScope(ViewModel 绑定)。
fun main() {
    // 创建自定义作用域
    val scope = CoroutineScope(Dispatchers.IO)

    scope.launch {
        repeat(5) {
            println("协程1:$it")
            delay(500)
        }
    }

    scope.launch {
        repeat(5) {
            println("协程2:$it")
            delay(500)
        }
    }

    // 等待 1 秒后取消作用域(所有子协程取消)
    Thread.sleep(1000)
    println("取消作用域")
    scope.cancel()
}

3. 作用域构建器

构建器 类型 行为
runBlocking 常规函数 阻塞当前线程直到完成
coroutineScope 挂起函数 挂起当前协程,创建子作用域,释放线程
supervisorScope 挂起函数 类似 coroutineScope,但子协程失败不影响兄弟协程
fun main() = runBlocking {
    coroutineScope {
        launch {
            delay(1000L)
            println("Task from nested scope")
        }
        delay(100L)
        println("Task from coroutine scope")
    }
    println("Scope is over")
}

4. 父子 Job 的层次关系

  • 父协程取消时,所有子协程自动取消;
  • 子协程异常时,默认会向上传播取消父协程;
  • 使用 supervisorScope 可以改变异常传播行为。

五、异常处理

1. 异常的传播

  • launch:异常立即抛出,可以通过 CoroutineExceptionHandler 处理;
  • async:异常在调用 .await() 时抛出,需要 try-catch 处理。
fun main() = runBlocking {
    // launch 异常处理
    val job = launch {
        try {
            throw RuntimeException("Error in launch")
        } catch (e: Exception) {
            println("Caught: ${e.message}")
        }
    }

    // async 异常处理
    val deferred = async {
        throw RuntimeException("Error in async")
    }
    try {
        deferred.await()
    } catch (e: Exception) {
        println("Caught: ${e.message}")
    }
}

2. CoroutineExceptionHandler

全局异常处理器,用于捕获未处理的异常:

val handler = CoroutineExceptionHandler { _, exception ->
    println("Caught: $exception")
}

fun main() = runBlocking {
    val job = launch(handler) {
        throw RuntimeException("Something went wrong")
    }
    job.join()
}

3. 监督作用域(Supervision)

普通作用域中,子协程失败会取消父协程和兄弟协程。监督作用域改变了这一行为:

fun main() = runBlocking {
    supervisorScope {
        val child1 = launch {
            try {
                delay(Long.MAX_VALUE)
            } finally {
                println("Child 1 cancelled")
            }
        }

        val child2 = launch {
            throw RuntimeException("Child 2 failed")
        }

        child2.join()
        child1.cancel() // 需要手动取消
    }
}

六、协程进阶用法

1. Flow:异步数据流

Flow 是协程的异步数据流,支持冷流、操作符链式调用,替代传统回调/回调流。

import kotlinx.coroutines.flow.*

fun getNumbers(): Flow<Int> = flow {
    for (i in 1..5) {
        delay(500)
        emit(i) // 发射数据
    }
}

fun main() = runBlocking {
    getNumbers()
        .filter { it % 2 == 0 }
        .map { it * 10 }
        .collect { println("接收数据:$it") }
}
// 输出:
// 接收数据:20
// 接收数据:40
特性 Channel Flow
类型 热流(hot) 冷流(cold)
数据生产 独立于消费 随收集而生产
适用场景 协程间通信 异步数据序列

2. Channel:协程间的管道

Channel 允许在不同协程之间传递数据流:

import kotlinx.coroutines.channels.Channel

fun main() = runBlocking {
    val channel = Channel<Int>()

    launch {
        for (x in 1..5) {
            channel.send(x * x)
        }
        channel.close()
    }

    for (y in channel) {
        println(y)
    }
}

3. 合并多个异步源

  • combine:合并两个 Flow
  • async/await:合并多个 Deferred
fun main() = runBlocking {
    // 合并两个 Flow
    val flow1 = flow { emit(1); delay(100); emit(2) }
    val flow2 = flow { emit("A"); delay(200); emit("B") }

    combine(flow1, flow2) { num, str -> "$num-$str" }
        .collect { println(it) }

    // 合并两个 async 结果
    val deferred1 = async { 10 }
    val deferred2 = async { 20 }
    println("合并结果:${deferred1.await() + deferred2.await()}")
}

4. 超时处理:withTimeout

设置协程执行超时时间:

fun main() = runBlocking {
    try {
        withTimeout(1000) {
            repeat(3) {
                println("执行中:$it")
                delay(600)
            }
        }
    } catch (e: TimeoutCancellationException) {
        println("协程超时")
    }
}

5. SharedFlow 与 StateFlow

用于在多个收集器之间共享状态:

  • StateFlow:持有一个可观察的状态值(类似 LiveData)
  • SharedFlow:可配置的事件流

七、协程的性能原理

1. 阻塞 vs 非阻塞

suspend fun blockingWork() {
    Thread.sleep(1000) // 阻塞当前线程(错误用法)
}

suspend fun nonBlockingWork() {
    delay(1000) // 挂起协程,释放线程
}

当使用 delay 时,协程挂起并释放线程,线程可以去执行其他协程。这是协程高效并发的基础。

2. 协程的轻量性验证

fun main() = runBlocking {
    repeat(100_000) {
        launch {
            delay(1000L)
            print(".")
        }
    }
}
// 内存消耗极小,运行流畅

如果用线程实现同样的逻辑,很可能会导致内存不足。


八、最佳实践

  1. 避免使用 GlobalScope:应使用自定义作用域或结构化并发;
  2. 为主线程安全设计:使用 withContext(Dispatchers.IO) 执行耗时操作;
  3. 合理选择调度器
    • UI 操作:Dispatchers.Main
    • CPU 密集型:Dispatchers.Default
    • IO 密集型:Dispatchers.IO
  4. 使用超时保护withTimeout 防止协程无限运行;
  5. 适当处理取消:计算密集型的协程应定期检查 isActive 或调用 yield()
  6. 使用监督作用域处理独立任务:一个子任务失败不应影响其他任务时;
  7. Android 开发优先使用 lifecycleScope / viewModelScope

九、官方资料链接

  1. Kotlin 协程官方文档(英文)https://kotlinlang.org/docs/coroutines.html
  2. Kotlin 协程中文文档https://www.kotlincn.net/docs/reference/coroutines.html
  3. Kotlin 协程核心指南(官方博客)https://blog.jetbrains.com/kotlin/2019/02/kotlin-1-3-coroutines/
  4. Flow 官方文档https://kotlinlang.org/docs/flow.html
  5. Android 协程最佳实践https://developer.android.com/kotlin/coroutines
  6. 通道(Channels)https://kotlinlang.org/docs/channels.html
  7. 异常处理https://kotlinlang.org/docs/exception-handling.html

总结

  1. 核心概念:协程是轻量级并发框架,通过 suspend 实现非阻塞挂起,Dispatchers 灵活切换线程,以同步写法实现异步逻辑。
  2. 基本使用launch 启动无返回值的协程,async 返回可等待的结果,runBlocking 桥接普通代码。
  3. 结构化并发:协程作用域管理协程生命周期,父子 Job 形成层次结构,自动传播取消和异常。
  4. 异常处理CoroutineExceptionHandler 捕获未处理异常,supervisorScope 改变异常传播。
  5. 协程通信Channel 用于协程间通信,Flow 处理异步数据流,StateFlow 共享状态。
  6. 性能原理:挂起释放线程,非阻塞等待实现高效并发,可启动数十万协程。

掌握协程是 Kotlin 进阶开发的关键一步,它让你能够以直观的顺序代码处理复杂的异步场景,大幅提升应用性能和可维护性。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐