并发和并行的区别是什么?一篇讲清概念、CPU调度和 Java 里的用法
并发和并行这道题看着基础,实际非常容易答得又空又乱。背书式回答只能说明你看过概念,真正能把它讲顺、讲透、还能落到 Java 代码里,面试官才会觉得你是理解了。本文不绕术语,先讲人话,再讲 CPU 调度,最后落到 Java 和面试答法。
为什么这道题总在面试里出现
这道题高频,不是因为它难,而是因为它特别适合区分“知道”和“懂了”。
面试官真正想看的,通常不是一句死定义,而是下面这三件事:
- 你能不能用大白话把两个概念讲清楚
- 你知不知道它背后对应的是 CPU 调度和硬件能力
- 你能不能顺手把它和 Java 多线程、线程池、并行计算联系起来
也就是说,这题表面在问术语,实际上在看你有没有形成完整的理解链路。
能把概念讲明白的人,不一定写过特别复杂的并发程序;但连概念都讲不顺的人,大概率写并发代码时也容易混。
并发和并行,一句话怎么区分
先记最短版本:
并发,是多个任务在一段时间内交替推进。并行,是多个任务在同一时刻真正一起执行。
如果你想说得更像面试回答一点,也可以这样讲:
- 并发(Concurrency):多个任务在逻辑上同时进行,核心是“调度”和“切换”
- 并行(Parallelism):多个任务在物理上同时执行,核心是“同时跑”和“多核”
下面这个表最适合背下来之后自己再展开:
| 对比项 | 并发 | 并行 |
|---|---|---|
| 核心理解 | 同时处理多件事 | 同时做多件事 |
| 是否要求多核 | 不一定 | 通常需要 |
| 执行方式 | 交替推进 | 同时执行 |
| 关键能力 | 调度能力 | 执行能力 |
| 常见场景 | IO 密集任务、请求处理 | 大规模计算、数据并行处理 |
很多人卡住,就是因为把“看起来同时”误当成了“真的同时”。这两个词最容易混的地方,也恰恰在这里。
用生活例子把两个概念讲直白
如果只讲定义,脑子里很容易是空的。所以面试里最稳的方式,通常是先举一个生活例子。
并发:一个人来回切着做
想象你一个人在厨房里同时准备两道菜:
- 切一会儿土豆
- 回头去翻炒鸡蛋
- 然后再回来切肉
- 再去调酱汁
从外面看,好像四件事都在推进。但本质上,你始终只有一个人,只是在不同任务之间快速切换。
这就是并发。
关键点不在于“真的同时干”,而在于“多个任务都没有被彻底搁置,而是在交替推进”。
并行:多个人同时干
再换一个画面,厨房里现在有两位厨师:
- 厨师 A 负责西红柿炒蛋
- 厨师 B 负责宫保鸡丁
两个人各干各的,动作互不影响,任务也是真正同时进行。
这就是并行。
所以可以把它们理解成:
- 并发像是“一个人高频切任务”
- 并行像是“多个人同时干活”
这个类比非常朴素,但特别好用。因为它一下就把“切换”和“同时”区分开了。
从 CPU 角度看:时间片轮转 vs 多核同时执行
如果想把这题从“会说”提升到“说得专业”,下一步就是把视角切到 CPU。
并发的底层:时间片轮转
在单核 CPU 上,一个时刻通常只能真正执行一个线程。
那为什么我们会感觉多个任务都在跑?
因为操作系统会把 CPU 时间切成很多很短的时间片:
- 线程 A 先执行一小会儿
- 时间片到了,切到线程 B
- 再切到线程 C
- 然后可能又切回线程 A
由于切换速度很快,人就会觉得它们“像是在一起跑”。
这就是并发最核心的底层基础:任务切换。
上下文切换为什么有成本
线程切换不是白来的。每切一次,系统都要做几件事:
- 保存当前线程的运行状态
- 恢复下一个线程的状态
- 更新程序计数器、寄存器等执行现场
这套动作就叫上下文切换。
所以线程不是越多越好。线程太多,CPU 可能不是在干活,而是在忙着“切来切去”。
并发能提高系统的任务组织能力,但不等于一定提升性能。如果线程数量远超机器承载能力,频繁上下文切换反而会把性能拖下去。
并行的底层:多核同时执行
并行就直接得多。
如果你的机器有多个 CPU 核心,那么多个线程就有机会被分配到不同核心上:
- 核心 1 跑线程 A
- 核心 2 跑线程 B
- 核心 3 跑线程 C
这时候不是“轮流来”,而是真正意义上的“大家一起跑”。
所以并行的前提往往是:硬件上有足够的执行单元。
并发和并行不是对立关系
这也是非常容易答错的一点。
很多人一听“并发”和“并行”,会下意识把它们理解成二选一。其实不是。
更准确的说法应该是:
- 并发是一种程序结构上的组织方式
- 并行是一种运行时的执行方式
换句话说,一个程序可以先被设计成并发的,再根据机器条件决定是不是以并行方式跑起来。
比如同一套程序:
- 在单核机器上,它可能只能并发,靠切换推进多个任务
- 在多核机器上,它不仅并发,还可能并行执行
所以这两者的关系,更像是:
并发描述的是“怎么安排任务”,并行描述的是“任务有没有真的同时执行”。
这句如果你能顺出来,整道题基本就已经稳了。
放到 Java 里该怎么理解
讲到这里,如果你还不能落到 Java,面试官通常还会继续追问。
1. Java 线程池更多体现的是并发能力
线程池最常见的作用,是让多个任务能被统一管理和调度。它天然就带有并发属性。
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ConcurrencyDemo {
public static void main(String[] args) {
ExecutorService executor = Executors.newFixedThreadPool(3);
executor.submit(() -> handleRequest("订单查询"));
executor.submit(() -> handleRequest("库存检查"));
executor.submit(() -> handleRequest("物流追踪"));
executor.shutdown();
}
private static void handleRequest(String taskName) {
System.out.println(Thread.currentThread().getName() + " 处理任务: " + taskName);
}
}这段代码的重点不是“3 个任务一定同时执行”,而是:
- 多个任务被同时提交
- 线程池负责调度它们
- 它们可以交替推进,也可能在多核机器上部分并行
所以你可以说:Java 的线程池先解决的是并发组织问题,至于是否并行,要看底层线程调度和 CPU 核数。
2. parallelStream()更强调并行处理
当你明确要把一批数据拆开,让多个核心一起算时,parallelStream() 就是一个很常见的入口。
import java.util.List;
public class ParallelStreamDemo {
public static void main(String[] args) {
List<Integer> numbers = List.of(1, 2, 3, 4, 5, 6, 7, 8);
numbers.parallelStream()
.map(ParallelStreamDemo::heavyCompute)
.forEach(result ->
System.out.println(Thread.currentThread().getName() + " -> " + result)
);
}
private static int heavyCompute(int value) {
return value * value;
}
}这时候更偏向的是“让多核一起干活”,所以它更接近并行计算。
但也别把它神化。parallelStream()适合的是可拆分、彼此独立、计算量比较明确的任务。如果任务很轻、共享状态很多,或者顺序要求很强,它未必划算。
3. ForkJoinPool是 Java 里典型的并行计算工具
ForkJoinPool 的思路很适合解释并行:
- 把大任务拆成小任务
- 小任务分散到多个工作线程
- 多个核心一起处理
- 最后把结果再汇总
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.RecursiveTask;
public class ForkJoinDemo {
public static void main(String[] args) {
ForkJoinPool pool = new ForkJoinPool();
int sum = pool.invoke(new SumTask(1, 100));
System.out.println("sum = " + sum);
}
static class SumTask extends RecursiveTask<Integer> {
private final int start;
private final int end;
SumTask(int start, int end) {
this.start = start;
this.end = end;
}
@Override
protected Integer compute() {
if (end - start <= 10) {
int result = 0;
for (int i = start; i <= end; i++) {
result += i;
}
return result;
}
int mid = (start + end) / 2;
SumTask left = new SumTask(start, mid);
SumTask right = new SumTask(mid + 1, end);
left.fork();
int rightResult = right.compute();
int leftResult = left.join();
return leftResult + rightResult;
}
}
}这类代码很适合用来说明:
- Java 不只是支持“多线程”
- 它还提供了专门面向并行计算的工具
4. 一句话总结 Java 里的对应关系
Thread、ExecutorService、CompletableFuture更常出现在并发场景parallelStream()、ForkJoinPool更常用来做并行计算
但这不是绝对边界。核心仍然是:你是在组织多个任务,还是在争取多个核心一起算。
面试时怎么用 30 秒答清楚
如果面试官问得比较基础,你完全可以直接用下面这套结构回答:
并发和并行的区别,核心在于一个是“交替推进”,一个是“同时执行”。
并发强调的是多个任务在一段时间内都在前进,哪怕底层只有一个 CPU 核心,也可以通过时间片轮转实现。
并行强调的是真正意义上的同时运行,通常依赖多核 CPU。
放到 Java 里,线程池更多体现的是并发调度能力,而像parallelStream()和ForkJoinPool这类工具,更偏向并行计算。
如果面试官继续追问,你再补下面两点:
- 并发的底层关键是上下文切换
- 并发和并行不是对立关系,而是“设计方式”和“执行方式”的区别
这套答法的优点是层次很清楚:
- 第一层:先下定义
- 第二层:解释底层
- 第三层:落到 Java
只要不乱,这题一般就不会翻车。
常见误区
误区 1:并发就是同时执行
不是。
并发可以只是“轮着来”,只是轮得很快,所以看起来像同时。
误区 2:线程越多,并发性能越高
也不是。
线程多到一定程度,CPU 会把大量时间浪费在上下文切换上,吞吐量反而可能下降。
误区 3:单核 CPU 就没有并发
单核 CPU 依然可以并发。
它做不到真正的并行,但完全可以通过任务切换让多个线程都获得推进机会。
误区 4:parallelStream()一定更快
不一定。
如果数据量小、任务本身很轻、拆分和合并的开销不小,parallelStream()反而可能更慢。
误区 5:并发和并行只能选一个
也不对。
很多程序在设计上是并发的,在多核机器上执行时又能体现出并行效果。它们经常是一起出现的。
总结
把这道题真正讲清楚,其实就抓住四句话:
- 并发是多个任务交替推进
- 并行是多个任务同时执行
- 并发靠调度,并行靠多核
- 并发是程序结构,并行是执行方式
如果只是背“逻辑同时”和“物理同时”,你最多算答到了表面;如果你能进一步讲到时间片轮转、上下文切换、Java 线程池和 ForkJoinPool,那这题基本就从“会背”变成“会讲”了。
面试里很多基础题都不是难在知识点本身,而是难在你能不能把它讲得清楚、讲得顺、讲得落地。并发和并行,就是一个很典型的例子。
参考资料:
