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 保存协程执行现场

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

相关推荐
体验家7 小时前
体验家 XMPlus 网页端问卷 SDK 技术解析:用几行 JavaScript 实现精准场景触发与防打扰机制
开发语言·前端·javascript
技术小结-李爽8 小时前
【工具】Maven的使用
java·maven
sou_time8 小时前
从 0 到 商用:AI Agent x SKILL x MCP 全栈实战教程:L2 高等篇:MCP 协议 + Spring AI + Agent 编排
java·人工智能·spring
二十七剑8 小时前
LangGraph 源码深度解析:Node 节点 Protocol 与 StateNodeSpec 核心机制
开发语言·python
AC赳赳老秦8 小时前
OpenClaw + 云数据库运维:自动备份、扩容、迁移 RDS/MySQL 云数据库
运维·开发语言·数据库·人工智能·python·mysql·openclaw
醉城夜风~8 小时前
类和对象III
开发语言·c++
冷小鱼8 小时前
高级研发编码习惯:从规范到艺术,再到AI+时代的人机协同
java·开发语言·python·编码习惯
齐 飞8 小时前
JDK21虚拟线程
java·后端
小马爱打代码8 小时前
Java 并发 Bug 深度分析与实战
java
fox_lht8 小时前
15.4.循环和迭代器的性能比较
开发语言·后端·学习·rust