Skip to content

并发 vs 并行 vs 串行

搞清楚这三个概念,并发编程就懂了一半。

先看现象

你一定见过这种情况:

电脑同时开着浏览器、微信、IDE——三个程序同时运行。
但你的 CPU 可能只有 4 核。
4 核怎么同时跑 3 个程序?还是说其实不是"同时"?

要回答这个问题,就得搞清楚并发并行串行这三个概念。

三种执行模式

串行:老老实实一个个来

一件事做完再做下一件,顺序执行,不可能有重叠。

java
// 串行执行
task1();  // 等 task1 完全结束
task2();  // 再开始 task2
task3();  // 最后 task3

// 总耗时 = task1 + task2 + task3

特点

  • 简单,不会有混乱
  • 效率低,CPU 经常闲着(等 I/O)
  • 适合任务之间有依赖的场景

并发:交替执行,假装同时

单核 CPU 同一时刻只能执行一个任务,但 CPU 切换速度快到人类感知不到,看起来就像"同时"在运行。

时间 ────────────────────────────────────────────→

CPU:  ████ task1 ████ task2 ████ task1 ████ task3 ████
      ↑                        ↑                        ↑
    处理中                   切换                     切换

I/O:       ░░░░ 等待中 ░░░░         ░░░░ 等待中 ░░░░
            ↑               ↑
          阻塞              阻塞

特点

  • 单核就能实现
  • 充分利用 CPU(一个任务等 I/O,切换去执行另一个)
  • 实质是"时分复用",快速切换

并行:真正的同时执行

多个任务在同一时刻同时执行,必须有多核 CPU 支持。

时间 ────────────────────────────────────────────→

CPU1: ████████████████████████████████  task1
CPU2: ████████████████████████████████  task2
CPU3: ████████████████████████████████  task3

同一时刻,三个任务真正同时运行

特点

  • 需要多核 CPU
  • 真正的同时执行,不切换
  • 效率最高,但受硬件限制

一张图说清楚

                    ┌─────────────────────────────────────┐
                    │           任务执行方式                │
                    └─────────────────────────────────────┘

            ┌───────────────────────┼───────────────────────┐
            ▼                       ▼                       ▼
      ┌──────────┐            ┌──────────┐            ┌──────────┐
      │   串行   │            │   并发   │            │   并行   │
      │          │            │          │            │          │
      │ 一个个来 │            │ 交替执行 │            │ 同时执行 │
      └──────────┘            └──────────┘            └──────────┘
            │                       │                       │
            ▼                       ▼                       ▼
      单线程顺序执行           单核快速切换              多核真正同时

      场景:                   场景:                   场景:
      - 简单任务               - I/O 密集型             - CPU 密集型
      - 有依赖关系的任务        - 提高资源利用率          - 大数据处理

实战区分

CPU 密集型 vs I/O 密集型

java
public class ConcurrencyVsParallel {

    public static void main(String[] args) {
        int cores = Runtime.getRuntime().availableProcessors();
        System.out.println("你的 CPU 有 " + cores + " 个核心");

        // CPU 密集型:计算为主,最好用并行
        // 并行 = CPU 核心数 个线程

        // I/O 密集型:等待为主,并发效果更好
        // 因为等待时 CPU 闲着,可以切换去干别的
        // 并发数 = CPU 核心数 * 2 或更多
    }
}

代码示例:并发下载文件

java
public class FileDownloader {

    // 串行下载:3 个文件,总耗时 = 文件1 + 文件2 + 文件3
    public void downloadSerial(List<String> urls) {
        for (String url : urls) {
            download(url);  // 同步等待
        }
    }

    // 并发下载:3 个文件同时下载,总耗时 ≈ max(文件1, 文件2, 文件3)
    public void downloadConcurrent(List<String> urls) {
        ExecutorService executor = Executors.newFixedThreadPool(10);
        for (String url : urls) {
            executor.submit(() -> download(url));
        }
        executor.shutdown();
    }

    private void download(String url) {
        // 模拟下载耗时
        System.out.println("开始下载: " + url);
        try {
            Thread.sleep(1000);  // 假设 1 秒
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("下载完成: " + url);
    }
}

输出对比(串行):

开始下载: 文件1
下载完成: 文件1
开始下载: 文件2
下载完成: 文件2
开始下载: 文件3
下载完成: 文件3
总耗时: 3 秒

输出对比(并发):

开始下载: 文件1
开始下载: 文件2
开始下载: 文件3
下载完成: 文件1
下载完成: 文件2
下载完成: 文件3
总耗时: 1 秒

常见误区

误区一:并发就是并行

很多人混用这两个词,但它们不是一回事:

并发并行
英文ConcurrentParallel
同一时刻只有一个任务在执行多个任务同时执行
硬件要求单核即可必须多核
比喻一心多用(切换很快)多人同时工作

误区二:线程越多越好

线程数 = CPU 核心数 * 2?错!

I/O 密集型:可以多线程(等待时让 CPU 干别的)
CPU 密集型:线程数 ≈ CPU 核心数(再多没意义,CPU 忙不过来)

线程太多反而有害:
- 线程创建和切换有开销
- 内存占用增加
- 上下文切换反而降低效率

误区三:并发一定能提速

java
// 场景:3 个任务,每个 100ms
// 串行:300ms
// 并发:100ms(理想情况)

// 但如果任务太短(比如只有 1ms),切换线程的开销可能比任务本身还大
// 这时候并发反而更慢

// 还有一种情况:任务之间有依赖,不能并行
task1();
task2(task1_result);  // task2 依赖 task1 的结果
task3(task2_result);  // task3 依赖 task2 的结果
// 只能串行

总结

模式同一时刻任务数硬件要求适用场景
串行1单核简单任务、有依赖
并发1(快速切换)单核I/O 密集、提高利用率
并行N多核CPU 密集、真正并行

记住:并发解决的是「抢着做」的问题,并行解决的是「一起做」的问题

下一节我们看进程和线程的关系,以及 Java 中如何操作它们。

基于 VitePress 构建