Home
avatar

YEYUbaka

并发和并行的区别是什么?一篇讲清概念、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 里的对应关系

  • ThreadExecutorServiceCompletableFuture 更常出现在并发场景
  • parallelStream()ForkJoinPool 更常用来做并行计算

但这不是绝对边界。核心仍然是:你是在组织多个任务,还是在争取多个核心一起算。


面试时怎么用 30 秒答清楚

如果面试官问得比较基础,你完全可以直接用下面这套结构回答:

并发和并行的区别,核心在于一个是“交替推进”,一个是“同时执行”。
并发强调的是多个任务在一段时间内都在前进,哪怕底层只有一个 CPU 核心,也可以通过时间片轮转实现。
并行强调的是真正意义上的同时运行,通常依赖多核 CPU。
放到 Java 里,线程池更多体现的是并发调度能力,而像 parallelStream()ForkJoinPool 这类工具,更偏向并行计算。

如果面试官继续追问,你再补下面两点:

  1. 并发的底层关键是上下文切换
  2. 并发和并行不是对立关系,而是“设计方式”和“执行方式”的区别

这套答法的优点是层次很清楚:

  • 第一层:先下定义
  • 第二层:解释底层
  • 第三层:落到 Java

只要不乱,这题一般就不会翻车。


常见误区

误区 1:并发就是同时执行

不是。

并发可以只是“轮着来”,只是轮得很快,所以看起来像同时。

误区 2:线程越多,并发性能越高

也不是。

线程多到一定程度,CPU 会把大量时间浪费在上下文切换上,吞吐量反而可能下降。

误区 3:单核 CPU 就没有并发

单核 CPU 依然可以并发。

它做不到真正的并行,但完全可以通过任务切换让多个线程都获得推进机会。

误区 4:parallelStream()一定更快

不一定。

如果数据量小、任务本身很轻、拆分和合并的开销不小,parallelStream()反而可能更慢。

误区 5:并发和并行只能选一个

也不对。

很多程序在设计上是并发的,在多核机器上执行时又能体现出并行效果。它们经常是一起出现的。


总结

把这道题真正讲清楚,其实就抓住四句话:

  • 并发是多个任务交替推进
  • 并行是多个任务同时执行
  • 并发靠调度,并行靠多核
  • 并发是程序结构,并行是执行方式

如果只是背“逻辑同时”和“物理同时”,你最多算答到了表面;如果你能进一步讲到时间片轮转、上下文切换、Java 线程池和 ForkJoinPool,那这题基本就从“会背”变成“会讲”了。

面试里很多基础题都不是难在知识点本身,而是难在你能不能把它讲得清楚、讲得顺、讲得落地。并发和并行,就是一个很典型的例子。


参考资料:

Java 并发 并行 面试题 2026