关系
协程与线程本质是依附关系,协程并不能脱离线程独立存在,它底层是基于Java线程池的并发框架。协程本身只是一个轻量级的任务(task),最终执行权依然交由底层的CoroutineDispatcher分发给JVM线程来完成。
区别
它们区别,可以从三个方向展开说”资源模型“,”执行机制“,”切换成本“。
资源模型(内核态 VS 用户态)
- Java线程属于操作系统,内核态线程,非常‘重’。创建一个线程通常需要1MB的栈内存,大量创建容易引发OOM.
- kotlin协程是用户态的轻量级实体,在内存中本质只是一个Continuation对象,只占几十kb。
执行机制(阻塞VS挂起)
- 在IO,CPU等耗时操作中,Java线程采用的是“阻塞(Blocking)”机制,会导致当前线程挂起,并不会释放资源,CPU资源会白白浪费。
- 协程采用“非阻塞的挂起与恢复(supend & Resume)”机制。在耗时任务操作中,协程会主动交出执行权(挂起),但不会阻塞其所在的底层线程。底层线程会被立刻释放,去调度执行其他协程。这极大地提升了单线程的吞吐量。
切换成本(OS调度 vs 状态机)
- “Java 线程的切换需要操作系统的参与,涉及昂贵的线程上下文切换(Context Switch) ,包含寄存器状态的保存和恢复,性能开销很大。
- 协程的切换完全在用户态完成。Kotlin 编译器通过 CPS(Continuation-Passing Style,后续传递风格)变换,将
suspend函数编译成了状态机(State Machine) 。协程的挂起和恢复,本质上只是内存中状态机label状态的流转,几乎没有性能损耗。”
| 维度 | Java 线程 (Thread) | Kotlin 协程 (Coroutine) |
|---|---|---|
| 层级 | 操作系统级 (内核态) | 语言级 (用户态) |
| 内存开销 | 大 (约 1MB) | 极小 (数十 KB,纯对象) |
| 耗时处理 | 阻塞底层执行 (Blocking) | 挂起自身,不阻塞底层 (Suspend) |
| 切换机制 | OS 上下文切换 (开销大) | 编译器 CPS 变换 / 状态机 (开销极小) |