并发和并行的区别是什么?

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

欢迎 加入小哈的星球 ,你将获得: 专属的项目实战(已更新的所有项目都能学习) / 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/

面试考察点

  1. 概念清晰度:面试官不仅仅是想知道你能不能背出定义,更是想看你能不能用生活化的例子把这两个概念讲清楚。能讲清楚,说明你真理解了;只会背定义,说明只是 "看过" 而非 "懂了"。

  2. 底层机制理解:考察你是否知道并发依赖 CPU 时间片轮转(上下文切换),并行依赖多核 CPU 同时执行。这涉及到操作系统层面的调度机制,能区分出你的知识深度。

  3. Java 关联:能不能把这两个概念和 Java 的多线程、ForkJoinPoolparallelStream 等联系起来,说明你有实战思维,不是纯背概念。

核心答案

一句话区分:并发是 "同时处理" 多件事,并行是 "同时做" 多件事。

对比维度并发(Concurrency)并行(Parallelism)
核心定义多个任务在 逻辑上 同时推进多个任务在 物理上 同时执行
硬件要求单核 CPU 即可实现需要 多核 CPU
执行方式交替执行(时间片轮转)真正同时执行
本质任务调度能力任务执行能力
类比一个人交替打电话和发邮件两个人分别打电话和发邮件

深度解析

一、生活类比——最直观的理解

  • 并发就像一个厨师同时准备两道菜:先切会儿西红柿,放下刀去搅鸡蛋,搅完鸡蛋又回来切西红柿。看起来两道菜在 "同时" 准备,但其实厨师是在两件事之间来回切换。关键点是:只有一个 CPU(厨师),靠频繁切换来营造 "同时" 的错觉。

  • 并行就像两个厨师各做各的菜:厨师 A 做西红柿炒蛋,厨师 B 做宫保鸡丁,两个人真正同时在工作,互不干扰。关键点是:有多个 CPU(厨师),每个核心独立执行一个任务。

二、从 CPU 角度理解

并发的底层是上下文切换

  • 时间片轮转:操作系统把 CPU 时间切成很小的时间片(通常几毫秒),每个线程分配一个时间片,轮流执行。因为切换速度极快,人感觉像是 "同时" 在运行。

  • 上下文切换有开销:每次切换需要保存当前线程的执行状态(寄存器、程序计数器等),然后加载下一个线程的状态。频繁的上下文切换会影响性能,这也是为什么线程数不是越多越好的原因之一。

并行的底层是多核同时执行

  • 多核 CPU 上,每个核心可以独立执行一个线程,是真正的物理并行,不需要来回切换。

三、它们的关系——不是对立,而是协作

这点很多人搞混:并发和并行 不是互斥的概念

Rob Pike(Go 语言之父)有句经典的话:

"Concurrency is about dealing with lots of things at once. Parallelism is about doing lots of things at once."

并发是关于 "同时处理" 很多事情。并行是关于 "同时执行" 很多事情。

换句话说:

  • 并发是一种 程序结构 设计——你把程序设计成多个独立任务可以交错推进
  • 并行是一种 执行方式——多个任务在物理上同时跑

一个并发程序在单核 CPU 上以交替方式运行,在多核 CPU 上则以并行方式运行。并发是设计,并行是执行。

四、Java 中的体现

// 并发:多线程在单核上交替执行,也可以在多核上并行执行
ExecutorService executor = Executors.newFixedThreadPool(4);
executor.submit(() -> task1());  // 线程 A
executor.submit(() -> task2());  // 线程 B
executor.submit(() -> task3());  // 线程 C

// 并行:明确利用多核同时计算
list.parallelStream().forEach(item -> process(item));

// ForkJoinPool:专为并行计算设计
ForkJoinPool pool = new ForkJoinPool(4);
pool.invoke(new RecursiveTask<Void>() {
    @Override
    protected Void compute() {
        // 任务拆分,多核并行执行
    }
});

Java 线程模型天然支持并发,而 ForkJoinPoolparallelStream 则是 Java 在并行计算上的支持。

面试高频追问

  1. 什么是上下文切换?有什么影响?
    • 线程切换时需要保存和恢复执行状态,这个过程有 CPU 开销。线程数过多时,上下文切换频繁,反而会降低性能。
  2. Java 中怎么利用多核并行?
    • ForkJoinPoolparallelStream()CompletableFuture 配合自定义线程池,都可以充分利用多核。
  3. 单核 CPU 有并行的意义吗?
    • 单核 CPU 无法真正并行执行多个线程(超线程除外),但并发仍然有意义——比如一个线程等 IO 时,另一个线程可以用 CPU。

记忆口诀

并发是 "同时应对",并行是 "同时干活"。一个是调度能力,一个是执行能力。

总结

面试答这道题,三层递进:先说清楚定义(逻辑同时 vs 物理同时),再从 CPU 调度角度解释底层机制(时间片轮转 vs 多核执行),最后点明它们不是对立概念——并发是程序结构,并行是执行方式。能把这三层讲明白,面试官就知道你是真理解了。