线程、进程、协程的区别是什么?

一则或许对你有用的小广告

欢迎 加入小哈的星球 ,你将获得: 专属的项目实战(已更新的所有项目都能学习) / 1v1 提问 / Java 学习路线 / 学习打卡 / 每月赠书 / 社群讨论

  • 新开坑项目: 《Spring AI 项目实战(问答机器人、RAG 增强检索、联网搜索)》 正在持续爆肝中,基于 Spring AI + Spring Boot3.x + JDK 21...点击查看;
  • 《从零手撸:仿小红书(微服务架构)》 已完结,基于 Spring Cloud Alibaba + Spring Boot3.x + JDK 17...点击查看项目介绍; 演示链接: http://116.62.199.48:7070/;
  • 《从零手撸:前后端分离博客项目(全栈开发)》 2 期已完结,演示链接: http://116.62.199.48/

这道题看似是操作系统的基础概念题,但现在 Go、Kotlin 协程这么火,面试官问这个的频率越来越高了。很多 Java 候选人对进程和线程还能说说,一提到协程就露馅。能把这三个概念的关系和本质讲清楚,基本就能看出你的技术视野有多宽。

面试考察点

  1. 操作系统基础:面试官不仅仅是想知道你背不背得出来定义,更是想看你能不能从 资源分配调度 两个维度来区分三者。理解了这两个维度,很多概念就串起来了。

  2. 协程认知:考察你是否了解协程的本质——用户态线程,以及它为什么比线程轻量。这块答好了说明你技术视野不局限于 Java。

  3. Java 关联:能不能把进程、线程、协程和 Java 的实际应用联系起来,比如 JVM 进程模型、Java 线程与 OS 线程的映射关系、Java 19 虚拟线程(协程的 Java 实现)。

核心答案

一句话概括三者的关系:进程是资源分配的基本单位,线程是 CPU 调度的基本单位,协程是用户态的轻量级线程。

上图展示了三者的层级包含关系。简单来说:

  • 进程 是操作系统分配资源的基本单位,每个进程有独立的内存空间
  • 线程 是进程内的执行单元,多个线程共享进程的资源,由操作系统调度
  • 协程 是线程内的用户态调度单元,由程序自己控制切换,对操作系统透明

核心对比表格:

维度进程线程协程
调度方操作系统操作系统用户程序
切换开销大(涉及内存空间切换)中(寄存器、栈切换)极小(用户态栈切换)
内存占用独立地址空间(MB 级)共享进程内存,独立栈(MB 级)几 KB
创建成本高(fork中(系统调用)极低(用户态对象)
并发数量几十~几百几百~几千几万~几百万
通信方式管道、消息队列、共享内存共享内存(需同步)共享内存(无需同步)
是否安全一个进程崩溃不影响其他一个线程崩溃可能拖垮整个进程一个协程挂起不影响其他

深度解析

一、进程(Process)

进程是操作系统资源分配的最小单位。每个进程都有自己独立的内存空间(代码段、数据段、堆、栈),进程之间互不干扰。

// Java 中每个运行的 JVM 就是一个进程
public static void main(String[] args) {
    // 获取当前进程信息
    ProcessHandle current = ProcessHandle.current();
    System.out.println("PID: " + current.pid());
    System.out.println("进程信息: " + current.info());
}

进程的核心特点:

  • 隔离性强:每个进程的内存空间独立,一个进程挂了不会影响其他进程。这也是 Chrome 每个标签页用独立进程的原因——一个页面崩了不影响其他页面。
  • 创建开销大:创建进程需要分配独立的内存空间、复制父进程的资源(写时复制),代价不低。
  • 通信复杂:进程间通信(IPC)需要管道、消息队列、共享内存等机制,比线程间通信麻烦得多。

二、线程(Thread)

线程是 CPU 调度的最小单位,也是操作系统层面并发执行的基本单位。同一个进程内的多个线程共享进程的内存空间和资源,但每个线程有自己独立的栈和程序计数器。

Java 线程和操作系统线程是 1:1 的关系(传统模型),每个 Java 线程都对应一个 OS 线程。

上图展示了线程切换的过程。核心问题在于:线程的调度由操作系统内核完成,每次切换都要从用户态切换到内核态,保存和恢复寄存器、程序计数器等上下文,还可能导致 CPU 缓存(TLB)失效。一次线程切换的开销大约在 1~10 微秒

听起来不多?但如果你有上万个线程频繁切换,这个开销就非常可观了。这也是为什么线程数不能无限创建——光上下文切换就能把 CPU 吃满。

三、协程(Coroutine)

协程,也叫 "用户态线程" 或 "轻量级线程"。和线程最大的区别是:协程的切换由程序自己控制,不需要操作系统内核参与

上图展示了协程切换的过程。因为协程完全在用户态完成调度,不需要陷入内核态,所以切换速度极快。而且协程不需要操作系统分配栈空间(线程栈通常是 1MB),一个协程可能只需要 几 KB 的内存。

这意味着什么?一台机器上跑几百个线程就觉得吃力了,但跑 几十万个协程 完全没问题。Go 语言之所以在高并发场景下这么能打,很大程度就是因为 goroutine(Go 的协程)足够轻量。

四、三者的切换开销对比

这是面试中最核心的对比,直接看数据更直观:

维度进程切换线程切换协程切换
切换模式用户态 ↔ 内核态用户态 ↔ 内核态纯用户态
需要切换地址空间✅ 是❌ 否(同进程)❌ 否
需要 OS 参与✅ 是✅ 是❌ 否
寄存器保存/恢复全量部分仅用户态寄存器
TLB 缓存影响失效(严重)可能失效不影响
典型耗时10~100 微秒1~10 微秒0.1~1 微秒

切换开销:进程 >> 线程 >> 协程,差了差不多 1~2 个数量级。

五、Java 中的协程——虚拟线程(Virtual Threads)

Java 19 引入了虚拟线程(JEP 444,正式版在 Java 21),这就是 Java 版的协程。

// Java 21+ 虚拟线程
Thread.startVirtualThread(() -> {
    System.out.println("我是虚拟线程,非常轻量!");
});

// 或者使用虚拟线程工厂创建线程池
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
    // 轻松创建上万个 "线程"
    for (int i = 0; i < 100_000; i++) {
        executor.submit(() -> {
            // IO 操作时,虚拟线程自动让出载体线程
            // 不阻塞 OS 线程
            return httpClient.send(request);
        });
    }
}

虚拟线程的核心设计:

  • M:N 调度模型:M 个虚拟线程映射到 N 个平台线程(OS 线程)上。JVM 内部自己管理调度,不再 1:1 对应 OS 线程。
  • 自动让出:当虚拟线程遇到 IO 阻塞(网络请求、文件读写等)时,自动让出底层的平台线程,给其他虚拟线程用。等 IO 完成后再恢复执行。
  • API 兼容:虚拟线程还是 java.lang.Thread,现有的 synchronizedReentrantLock、线程池等 API 都能直接用,迁移成本很低。

这块面试的时候提一嘴虚拟线程,面试官绝对加分。说明你不仅在跟 Java 的发展,还理解协程的本质和 Java 的实现方式。

六、常见误区

误区 1:协程比线程快,所以协程能替代线程。

不完全对。协程在 IO 密集型 场景下确实优势巨大(大量 IO 等待,线程切换浪费严重)。但在 CPU 密集型 计算场景下,协程和线程的性能差距不大,甚至线程可能更快(因为线程可以真正并行利用多核,而同一时刻一个线程上只有一个协程在运行)。

误区 2:Java 没有协程。

Java 21 的虚拟线程就是协程。虽然实现方式(M:N 模型 + 自动 yield)和 Go 的 goroutine 不完全一样,但核心思想一致——用户态调度、轻量级、高并发。

误区 3:多线程就一定能利用多核。

多线程只是提供了利用多核的 可能性。但如果线程都在等锁、等 IO,那 CPU 照样闲着。协程的思路不一样——用少量线程 + 大量协程,在 IO 等待时快速切换到其他协程,把 CPU 吃满。

面试高频追问

  1. Java 线程和 OS 线程是什么关系?

    • 传统 Java 线程是 1:1 模型,每个 Java 线程对应一个操作系统线程。Java 21 的虚拟线程改为 M:N 模型,多个虚拟线程映射到少量 OS 线程上。
  2. 协程为什么轻量?

    • 三个原因:用户态调度不需要内核参与、栈空间只需要几 KB(线程通常 1MB)、创建和切换不需要系统调用。
  3. Go 的 goroutine 和 Java 虚拟线程有什么区别?

    • 核心思想一致,但实现细节不同。goroutine 使用自己的调度器(GMP 模型),栈初始只有 2KB 且可动态伸缩;虚拟线程基于 ForkJoinPool 调度,API 上完全兼容 Thread

常见面试变体

  • "为什么有了线程还需要协程?"
  • "Java 21 的虚拟线程是什么?解决了什么问题?"
  • "Go 的 goroutine 为什么比 Java 线程轻量?"

记忆口诀

进程分配资源,线程调度执行,协程用户态切换。创建成本和切换开销:进程 > 线程 > 协程。协程的优势在 IO 密集,不在 CPU 密集。

总结

面试答这道题,三层递进:先用一句 "进程是资源分配单位、线程是调度单位、协程是用户态线程" 给出核心结论,再从切换开销角度讲清楚三者的性能差异(关键点:内核态 vs 用户态),最后点一下 Java 21 虚拟线程作为加分项。能把内核态切换和用户态切换的区别讲明白,这道题就稳了。