安卓白话文面试(4) —— Kotlin协程?更轻量级的“流水线工人”

4 阅读5分钟

用生活比喻讲透:协程到底是什么?凭什么比线程轻量?


开场比喻:餐厅里的服务员 vs 后厨的专职厨师

你去一家网红餐厅吃饭,发现每个服务员同时照顾五六桌客人。这边点完菜,转头去给另一桌上菜,再回来收空盘子——一个人干了好几个人的活

但后厨不一样:切菜的只切菜,炒菜的只炒菜,一个萝卜一个坑。要是让切菜师傅也去炒菜,那叫线程切换,成本高得要命。

Kotlin 协程,就是那个眼观六路、耳听八方的服务员。它可以在一个线程上“同时”处理很多任务,遇到需要等的事情(比如等菜出锅)就先去招呼别的客人,等菜好了再回来接着干。


一、线程是“专职员工”,协程是“临时工”

你想想,线程就像公司里的正式员工:每个人都有固定的工位(栈内存)、固定的办公设备(CPU时间片)。招聘一个线程成本不低,要是让1000个线程同时干活,公司得有一整栋楼才行。

协程就不一样了,它是临时工——不需要独立工位。几个协程可以挤在一个线程上,轮流用同一张桌子。线程说“我这个岗位只能一个人”,协程说“咱们排个队,谁有事谁先让一下”。
但注意:协程虽然是“临时工”,但它有完整的上下文保存机制——就像临时工也戴着自己的工牌(continuation),随时能被叫回来继续干刚才的活,不会丢失进度。

技术上就是:线程由操作系统调度,切换时需要保存寄存器、刷新CPU缓存,很重。协程由程序自己调度,切换就像调用一个普通函数,只需要保存几个变量,成本低到可以忽略

所以你能开几万个协程,但开几千个线程手机就会卡成PPT。


二、挂起函数:“我去抽根烟,你先上”

协程最精妙的机制是挂起(suspend)。还是服务员例子:客人点完菜,服务员把单子递给后厨,然后不会傻站着等——而是挂起自己这个任务,去服务别的客人。等菜做好了,再回来继续上菜。

这就是非阻塞等待。换成线程呢?如果等网络请求返回,线程会真的卡在那里,什么事也干不了,白白浪费资源。
注意区分:挂起(suspend)是非阻塞的等待——线程去干别的协程了;阻塞(block)是线程真的卡死(比如在协程里调用Thread.sleep)。如果你在挂起函数里写阻塞代码,照样会堵死线程,协程的优势就没了。

代码里是这样的

suspend fun fetchUser() : User {
    // 发起网络请求,然后挂起自己,不阻塞线程
    return withContext(Dispatchers.IO) { api.getUser() }
}

挂起时,协程会把自己的“现场”保存到一个很轻的对象里,线程可以转头去跑别的协程。等网络回来,协程再从那个点恢复执行。

调度器和结构化并发:协程不是绑死在一个线程上,Kotlin提供了Dispatchers.Main(UI线程)、Dispatchers.IO(网络/数据库)、Dispatchers.Default(CPU计算)。你可以自由切换,就像服务员可以去前厅、后厨、仓库。更妙的是,协程有父子关系,父协程取消时所有子协程自动取消,这叫结构化并发,避免资源泄漏。

说白了就是:遇到耗时操作,协程主动让出线程,让别人先干。不像线程那样独占线程不释放。


三、协程就是“带状态机的普通函数”

再深入一点点(放心,不难)。你可以把协程理解成一个能暂停、能恢复的函数。编译器会偷偷把你的挂起函数改造成一个状态机——每次挂起的地方是一个状态,恢复时看该执行哪一步。

这就像你看视频:暂停了,进度条记住了时间点,点继续就接着播。线程呢?线程是流水线传送带,卡住就整条线停;协程是带书签的多任务手册,翻到哪页读哪页。

所以协程不需要操作系统帮忙,自己就把调度玩明白了。它减少了线程间锁的竞争(因为大部分时间在同一个线程上跑),但协程间的共享状态仍需注意原子性。


总结表格:线程 vs 协程

对比项线程协程
调度者操作系统程序自己(Kotlin运行时)
切换成本高(系统调用、保存寄存器)极低(就像函数调用)
并发数量几千个就崩几十万个轻轻松松
等待时的行为阻塞(线程卡住)挂起(线程继续干别的)
内存占用每个线程约1MB栈每个协程约几十字节

面试官爱怎么问?

问:协程比线程轻量,到底轻在哪?

答:线程的轻量是资源上的轻。线程切换要操作系统介入,保存一大堆CPU状态,内存也大。协程切换就是普通的函数调用,编译器帮你做了状态机,不需要内核参与。打个比方:线程像搬家,得请搬家公司;协程像换个座位,站起来走两步就行。

问:挂起函数和普通函数有什么区别?

答:普通函数一跑到底,中间不能停。挂起函数可以主动让出线程,等耗时操作完成后再回来继续跑。就像你去办事,排队时不是死等,而是先干别的,快排到你了再回来。注意:挂起是非阻塞的,线程不会卡死;如果你在挂起函数里写Thread.sleep(),那就变成阻塞了,协程的优势就没了。


三句话人话总结

  1. 资源层:协程内存占用极小(几十字节),一个线程能跑成千上万个协程,像纸飞机对比战斗机。
  2. 调度层:挂起是协作式让权,切换在用户态完成,遇到IO就主动让出线程,绝不阻塞。
  3. 适用层:协程适合IO密集型任务(网络、数据库),不适合纯计算密集型(排序、加密)——后者该用线程池。

汇总导航

安卓白话文面试导航