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

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

相关推荐
jimy11 小时前
C语言中的inline function specifier(函数说明符、关键字)
c语言·开发语言
手揽回忆怎么睡1 小时前
springboot3使用ProGuard混淆jar
java·jar
dadaobusi2 小时前
PCIe的ATS和PRS
java·网络·数据库
南境十里·墨染春水2 小时前
线程池学习(二)线程池理解
java·jvm·学习
ZGi.ai2 小时前
私有化大模型接入企业系统:SSO+权限+API网关完整方案
java·开发语言·大模型·私有化部署·sso·企业架构
Han_han9192 小时前
集合进阶(Map集合):
java
吴声子夜歌2 小时前
Java——文件和目录操作
java·文件·目录
吴声子夜歌2 小时前
Java——随机
java·random·随机
一念春风2 小时前
记事本(C#)
开发语言·c#