Kotlin 协程底层原理(Continuation)详解

Kotlin 协程底层原理(Continuation)详解

很多人会用协程:

kotlin 复制代码
launch { }
async { }
withContext { }

但真正理解协程底层后,你会发现:

Kotlin 协程本质上并不是"魔法异步"。

它本质是:

Continuation(续体) + 状态机

这才是协程真正核心。


一、先理解:为什么需要 Continuation?

先看普通函数。

普通函数执行过程

kotlin 复制代码
fun test() {
    println("1")
    println("2")
    println("3")
}

执行流程:

复制代码
1 -> 2 -> 3

函数执行完:

  • 栈帧销毁
  • 无法恢复

这叫:一次性执行。

但协程不同。


二、协程为什么能"暂停后恢复"?

kotlin 复制代码
suspend fun load() {
    println("开始")
    delay(3000)
    println("结束")
}

问题来了:

delay 后,协程暂停了。3秒后,它为什么还能继续从 println("结束") 开始执行?

普通函数做不到。

所以 Kotlin 编译器做了一件事:

把 suspend 函数改造成**"状态机"**

而 Continuation,就是保存状态的核心对象。


三、Continuation 是什么?

官方定义:

"协程执行到哪了"的记录器。

或者更简单:

Continuation = 协程存档点

它会记录:

  • 当前执行位置
  • 局部变量
  • 下一步执行逻辑
  • 协程上下文

四、最简单理解

你玩游戏:

  • 打到一半存档
  • 退出游戏
  • 下次继续

Continuation,就是这个"存档"。


五、suspend 本质是什么?

先看:

kotlin 复制代码
suspend fun request(): String

很多人以为 suspend 是关键字魔法。

其实编译后 ,suspend 函数会变成:

kotlin 复制代码
fun request(
    continuation: Continuation<String>
): Any

重点来了:

所有 suspend 函数,都会多一个 Continuation 参数

这是协程底层最大核心。


六、编译器偷偷干了什么?

你写:

kotlin 复制代码
suspend fun test() {
    delay(1000)
    println("完成")
}

编译器会生成类似:

kotlin 复制代码
class TestContinuation : Continuation<Unit> {
    var label = 0

    override fun resumeWith(result: Result<Unit>) {
        when(label) {
            0 -> {
                label = 1
                delay(1000, this)
            }
            1 -> {
                println("完成")
            }
        }
    }
}

这就是:

状态机(State Machine)


七、label 是什么?

kotlin 复制代码
var label = 0

它表示:当前协程执行到了哪个阶段

示例

kotlin 复制代码
suspend fun demo() {
    println("A")
    delay(1000)
    println("B")
    delay(1000)
    println("C")
}

编译后类似:

label 位置
0 开始
1 第一个delay后
2 第二个delay后

所以:

协程恢复时,只需要看 label,就知道该从哪里继续。


八、delay 为什么不会阻塞线程?

这是经典面试题。

Thread.sleep

kotlin 复制代码
Thread.sleep(3000)

线程:彻底卡死

delay

kotlin 复制代码
delay(3000)

发生了什么?

第一步:协程保存现场

  • Continuation 存档
  • 包括:label、局部变量、执行位置

第二步:当前线程被释放

  • 线程去执行别的任务

第三步:时间到了

  • 恢复 Continuation:continuation.resume()
  • 协程继续执行

所以:delay 挂起的是协程,不是线程。


九、resume 是什么?

Continuation 最核心方法:

kotlin 复制代码
resumeWith()

作用: 恢复协程执行

举例:

  • 协程暂停在 delay
  • 时间到 → resume()
  • 协程继续

十、真正的协程恢复流程

代码

kotlin 复制代码
launch {
    delay(1000)
    println("Hello")
}

底层流程

复制代码
1. launch 创建协程
   → 生成:Continuation对象

2. 执行到 delay
   → 发现:需要挂起

3. 保存状态
   → 保存:label、局部变量、上下文

4. 当前线程释放
   → 线程不阻塞

5. 1秒后
   → 定时器触发:continuation.resume()

6. 状态机恢复
   → 从:label = 1 继续执行

十一、协程为什么这么轻量?

线程 协程
OS内核调度 用户态对象
成本高 只是一堆 Continuation + 状态机对象

所以:

一个线程可以跑几十万个协程


十二、挂起点(Suspension Point)

只有 suspend 函数才能挂起。

例如:

  • delay()
  • withContext()
  • await()

这些都叫:挂起点


十三、为什么 suspend 只能在协程里调用?

因为:

suspend 函数需要 Continuation

普通函数没有。

所以:

kotlin 复制代码
fun main() {
    delay(1000) // 会报错
}

十四、withContext 为什么能切线程?

kotlin 复制代码
withContext(Dispatchers.IO)

本质:

  • 修改 Continuation 的 Dispatcher
  • 恢复协程时,调度器决定在哪个线程继续执行

十五、CoroutineContext

协程上下文,内部保存:

  • Dispatcher
  • Job
  • CoroutineName
  • ExceptionHandler

本质: 就是 Map 结构


十六、Job 底层原理

每个协程都有 Job,Job 维护:

  • 父子协程关系
  • cancel() 本质:修改协程状态,然后挂起点检测取消

十七、为什么协程取消不是立刻停止?

因为:协程是协作式取消,不是线程强杀。

例如:

kotlin 复制代码
while(true) { } // 不会停,因为没有检查取消状态

正确:

kotlin 复制代码
while(isActive) { }

十八、suspend 不等于异步

这是最容易误解的。

错误理解 正确理解
suspend = 开新线程 suspend 只是支持挂起与恢复,是否异步看你是否 launch / async

十九、真正的协程核心结构

协程底层可以简化成:

复制代码
Coroutine
    ↓
Continuation
    ↓
StateMachine
    ↓
Dispatcher

二十、协程 vs 回调

回调地狱

kotlin 复制代码
request {
    request2 {
        request3 {
            // ...
        }
    }
}

协程

kotlin 复制代码
val a = request()
val b = request2()
val c = request3()

为什么能这样? 因为 Continuation 替你保存了执行现场。


二十一、Retrofit 为什么支持 suspend?

Retrofit 遇到 suspend fun getUser():

本质: 它拿到了 Continuation

网络返回后,调用 continuation.resume(data),协程恢复。


二十二、最重要的一张图

普通函数

复制代码
执行 -> 结束 -> 消失

协程

复制代码
执行
    ↓
挂起
    ↓
保存Continuation
    ↓
线程释放
    ↓
恢复resume
    ↓
继续执行

二十三、协程源码里最重要的类

真正核心:

  1. Continuation(基类)
  2. ContinuationImpl
  3. BaseContinuationImpl
  4. DispatchedContinuation

二十四、源码中最经典的方法

kotlin 复制代码
resumeWith()

协程恢复入口。


二十五、面试高频题(非常重要)

1. suspend 本质是什么?

本质:编译器 CPS 转换

即 Continuation Passing Style

编译器自动添加 Continuation 参数。

2. 协程为什么不卡线程?

因为:挂起的是协程,不是线程

3. 协程为什么轻量?

因为:不走系统线程调度,本质只是对象和状态机

4. delay 为什么能恢复?

因为:continuation.resume()

5. 协程如何记住执行位置?

通过:label 状态机


二十六、真正理解协程的一句话

Kotlin 协程本质:

编译器把 suspend 函数转换成状态机

然后 Continuation 保存协程执行现场

最终实现:可暂停、可恢复、非阻塞

相关推荐
晗夜未央1 分钟前
任务队列反复失败,先分清重试、阻塞和结果待核对
开发语言·javascript
熊猫钓鱼>_>4 分钟前
四种书签,一条时间线:一个读书 App 的记忆颗粒与它的边界案例【鸿蒙心迹】
开发语言·华为·ai编程·harmonyos·鸿蒙·openharmony·鸿蒙pc
Fuyo_111928 分钟前
C++ AVL树保姆级详解:从平衡因子到四大旋转机制
java·c++·算法
Goway_Hui33 分钟前
【KMP for OpenHarmony】-- 在鸿蒙工程中使用 Apollo-Kotlin
华为·kotlin·harmonyos
努力努力再努力wz39 分钟前
【Lua 入门系列】Lua 函数深入理解:函数对象、多返回值、可变参数、闭包、递归与尾调用优化
开发语言·lua
Sarvartha1 小时前
Date、LocalDate、LocalTime、LocalDateTime、DateTimeFormatter类
java
阿俊-全栈开发1 小时前
LikeShop 多商户 Java 版商城微服务模块拆分与部署架构演进实践
java·微服务·架构·likeshop开源商城·多商户源码
飞浪纪元[FWC–FE]1 小时前
从零到一键绿:Qt 5.14.2 + VS2019 开发环境搭建、Kit 套件配置与构建错误修复完全手册
开发语言·qt
Wang's Blog1 小时前
Java 项目实战:交友项目 - 联系人列表
java·开发语言·交友
我命由我123452 小时前
访客系统形式(有用户概念、无用户概念)
java·开发语言·前端·javascript·python·产品运营·产品经理