进程、线程、并发与并行 | JavaSE

进程、线程、并发与并行

一、学习目标

完成本章后,你应该能够:

  • 准确解释进程(Process)、线程(Thread)、多线程(Multithreading)的基本概念。
  • 说明一个 Java 程序中的进程与线程之间是什么关系。
  • 区分单线程程序与多线程程序。
  • 准确区分并发(Concurrency)与并行(Parallelism),避免把二者混为一谈。
  • 理解 CPU、操作系统、JVM 与线程调度之间的大致关系。
  • 判断一个业务场景是否适合使用多线程。
  • 理解多线程能够解决什么问题,同时知道多线程并不意味着程序一定更快。
  • 建立后续学习 ThreadRunnable、线程安全、锁和线程池所需要的整体模型。

二、核心知识

2.1 从“执行流程”理解线程

先看一个最普通的 Java 程序:

public class Main {
    public static void main(String[] args) {
        System.out.println("任务 A");

        for (int i = 0; i < 5; i++) {
            System.out.println(i);
        }

        System.out.println("任务 B");
    }
}

程序大致按照:

main()
 ↓
任务 A
 ↓
for 循环
 ↓
任务 B

一路向下执行。

可以把这种:

从某个入口开始,按照一定顺序不断执行代码的过程

理解为一条执行流程(Execution Flow)

而线程(Thread),就是程序中的一条执行流程。

Java 官方对 Thread 的描述也非常直接:

一个线程就是程序中的一条执行线程。

如果整个程序只有一条主要执行流程,那么它就是典型的:

单线程程序

如果程序中同时存在多条执行流程:

执行流程 1
执行流程 2
执行流程 3
...

就进入了:

多线程

2.2 什么是进程

进程(Process)可以先理解为:

正在运行的程序实例。

例如你同时打开:

IntelliJ IDEA
Chrome
微信
网易云音乐

操作系统看到的不是四份静态的磁盘文件,而是多个正在运行的程序实例。

这些正在运行的程序,就是进程。

例如:

Chrome.exe
IDEA
java.exe

都可能对应一个或多个正在运行的进程。

对于一个普通 Java 程序:

java Main

操作系统会启动一个 JVM 进程。

这个 JVM 进程内部再运行 Java 程序。

可以暂时建立这样的模型:

操作系统

└── JVM 进程
    └── Java 程序

进程通常拥有相对独立的:

  • 虚拟地址空间;
  • 内存资源;
  • 文件句柄;
  • 系统资源;
  • 运行状态。

不同进程之间默认具有较强的隔离性。


2.3 什么是线程

线程(Thread)是:

进程内部的一条执行流程。

因此:

进程
└── 线程

不是两个毫无关系的东西。

而应该理解为:

一个进程
    ↓
内部可以存在多个线程

例如:

JVM 进程
├── main 线程
├── 工作线程 A
├── 工作线程 B
└── JVM 自身运行所需要的其他线程

同一个进程中的线程通常共享这个进程中的大量资源,例如:

堆内存中的对象
打开的文件
网络连接
进程级资源

这也是后面“线程安全问题”产生的重要根源:

多个线程
   ↓
同时访问
   ↓
同一个共享数据

就可能发生竞争。

不过线程安全将在后续 08-06 专门讨论,本章暂时只建立概念。


2.4 进程和线程的关系

可以用一个非常形象的比喻理解。

假设:

星雨笔录开发工作室 = 一个进程

里面有:

程序员 A
程序员 B
程序员 C

分别做:

A:写前端
B:写后端
C:写测试

那么三个程序员就类似三条线程。

工作室拥有共同资源:

办公室
服务器
代码仓库
项目文档

类似于同一个进程中的线程共享进程资源。

因此:

| 对比 | 进程 | 线程 | | -------- | ------------------ | ---------------------------- | | 基本含义 | 正在运行的程序实例 | 程序中的一条执行流程 | | 关系 | 可以包含多个线程 | 属于某个进程 | | 资源隔离 | 不同进程相对独立 | 同进程线程共享大量资源 | | 创建成本 | 通常较高 | 通常低于创建完整进程 | | 通信 | 进程间通信相对复杂 | 共享内存更加直接 | | 风险 | 隔离较强 | 共享数据容易产生并发安全问题 |

可以记住一句话:

进程是程序运行的容器,线程是容器中的执行流程。


2.5 Java 程序中的 main 线程

当 JVM 启动一个普通 Java 应用程序后,会执行:

public static void main(String[] args)

运行 main() 方法的线程通常称为:

main 线程

例如:

public class Main {
    public static void main(String[] args) {
        System.out.println("Hello Java");
    }
}

从初学者视角看:

JVM
 ↓
main 线程
 ↓
main()
 ↓
执行代码

在前面学习:

  • 数组;
  • 集合;
  • Stream;
  • File;
  • IO;

的时候,大多数练习实际上都是:

main 线程自己从头执行到尾。

接下来学习多线程,就是准备让程序从:

只有一条执行流程

发展成:

多条执行流程共同工作

2.6 什么是多线程

多线程(Multithreading)指:

一个程序中存在多条可以并发执行的线程。

例如:

Java 程序

main线程       下载线程       日志线程
   │              │              │
   ▼              ▼              ▼
处理界面        下载文件        写入日志

如果只有一个线程:

先下载
 ↓
下载完成
 ↓
再处理界面
 ↓
再写日志

那么某个耗时任务可能长期占据唯一执行流程。

使用多线程之后:

线程 A → 处理用户操作
线程 B → 下载文件
线程 C → 写日志

多个任务便可以在时间上重叠推进。

这就是多线程存在的重要意义。


2.7 什么是并发

并发(Concurrency)描述的是:

多个任务在一段重叠的时间范围内共同向前推进。

注意:

并发 ≠ 必须在同一个瞬间真的执行

假设 CPU 只有一个可执行核心。

有两个线程:

线程 A
线程 B

CPU 可以快速切换:

A 执行一会
 ↓
B 执行一会
 ↓
A 再执行
 ↓
B 再执行

形成:

时间 ─────────────────────>

CPU:
AAAA | BBB | AAA | BB | AAAA

宏观上看:

A 和 B 都在推进

于是构成并发。

这种现象经常被形象地描述成:

CPU 在多个任务之间快速切换,看起来好像同时执行。

但要注意:

“时间片轮转”是一种实现并发的方式,而不是并发本身的完整定义。

并发更重要的是:

多个任务的生命周期发生重叠
并且都在推进

2.8 什么是并行

并行(Parallelism)指:

多个任务在同一个时刻真正同时执行。

例如一颗多核心 CPU:

CPU Core 1 → 线程 A
CPU Core 2 → 线程 B
CPU Core 3 → 线程 C
CPU Core 4 → 线程 D

那么某个瞬间:

A、B、C、D

确实可能同时运行。

这就是并行。


2.9 并发与并行的区别

这是多线程学习中必须彻底理解的一组概念。

| 对比 | 并发 Concurrency | 并行 Parallelism | | --------------------- | ------------------------------ | ------------------------------- | | 核心含义 | 多个任务在重叠时间段内共同推进 | 多个任务在同一时刻真正执行 | | 是否要求多个 CPU 核心 | 不要求 | 通常需要多个执行资源 | | 单核 CPU | 可以并发 | 通常无法实现真正的 CPU 并行执行 | | 多核 CPU | 可以并发 | 也可以并行 | | 关注重点 | 多任务组织与协调 | 同时计算 |

可以用餐厅理解。

只有一个厨师:

切菜 2 分钟
炒菜 1 分钟
煮汤等待时去切菜
再回来处理汤

他在多个任务之间来回切换。

属于:

并发

如果现在有:

厨师 A → 炒菜
厨师 B → 煮汤
厨师 C → 切菜

同一时刻真的都在工作。

属于:

并行

2.10 并发与并行可以同时存在

现代计算机通常是多核心 CPU。

假设存在:

10 个线程
4 个 CPU 核心

某个瞬间最多只有其中若干线程真正占据 CPU 核心:

Core 1 → T1
Core 2 → T3
Core 3 → T7
Core 4 → T9

这些线程:

T1、T3、T7、T9

之间可能是并行执行。

但全部:

T1 ~ T10

又可能通过调度不断切换。

因此整个系统经常同时具有:

并发
+
并行

而不是“只能二选一”。


2.11 多线程为什么有价值

多线程的价值并不是简单一句:

“可以让程序更快。”

它主要解决几类问题。

场景一:提高程序响应能力

例如桌面程序正在下载一个 5 GB 文件。

如果下载和界面处理都由同一个线程承担:

开始下载
 ↓
长时间阻塞
 ↓
界面无法及时处理用户操作

用户可能看到:

程序未响应

如果拆开:

线程 A → UI
线程 B → 下载

界面仍然能够响应。


场景二:处理多个独立任务

例如聊天服务器同时面对:

用户 A
用户 B
用户 C
用户 D
...

如果所有客户端只能排队:

先服务 A
A 完全结束
再服务 B
B 完全结束
...

吞吐能力会非常差。

因此网络服务器天然存在:

并发处理多个连接

的需求。

这也是后面的 Java 网络编程为什么会再次遇到多线程。


场景三:利用多核 CPU

如果存在多个彼此独立的 CPU 密集型任务:

计算 A
计算 B
计算 C
计算 D

合理并行可能充分利用多个 CPU 核心。


场景四:重叠等待时间

大量程序并不是一直计算。

例如:

读取文件
访问数据库
发送网络请求
等待用户输入

都可能发生等待。

一个线程等待期间,另一个线程可以继续工作。


三、使用方法

本章还不要求你正式创建线程。

当前最重要的是:

看到一个程序需求后,先判断它属于什么执行模型。

3.1 判断是否存在多个独立任务

例如:

上传头像
发送消息
保存日志
接收新消息

这些任务并不一定必须严格按:

1 → 2 → 3 → 4

串行执行。

那么就可能存在并发价值。


3.2 判断任务是不是必须等待

例如:

读取 10 GB 文件
网络下载
数据库查询
Socket 等待消息

如果唯一线程一直等待:

整个程序其他任务也无法继续

这类场景通常需要考虑并发设计。


3.3 判断任务之间是否存在共享数据

例如:

线程 A ─┐
线程 B ─┼→ Account.balance
线程 C ─┘

此时虽然非常适合多线程处理多个用户请求,但同时出现新的问题:

共享数据
+
并发访问
=
线程安全风险

后面会专门学习:

synchronized
Lock

来解决这类问题。


四、原理与进阶

4.1 CPU 调度的基本认识

程序创建出多个可运行线程后,并不意味着程序员可以精确规定:

线程 A 先运行 3 ms
然后 B 运行 5 ms
然后 C 运行 2 ms

真正的执行受到很多因素影响,例如:

  • 操作系统调度;
  • CPU 核心数量;
  • 当前系统负载;
  • 线程状态;
  • I/O 等待;
  • JVM 实现;
  • 调度策略。

因此多线程程序的一个基本特征就是:

执行顺序通常具有不确定性。

后面看到:

主线程输出:0
子线程输出:0
子线程输出:1
主线程输出:1

或者:

子线程输出:0
主线程输出:0
主线程输出:1
子线程输出:1

都可能是正常现象。

不要把某一次运行时看到的顺序当成语言规则。


4.2 多线程不一定更快

假设任务只有:

1 + 1

却为了它:

创建线程
调度线程
切换线程
销毁线程

线程管理本身反而产生额外成本。

多线程会带来:

  • 线程创建成本;
  • 调度成本;
  • 上下文切换(Context Switch);
  • 内存占用;
  • 同步成本;
  • 线程安全问题;
  • Debug 难度;
  • 死锁等并发风险。

所以:

多线程是一种解决并发问题的工具,而不是“让所有代码自动变快”的魔法。


4.3 JDK 21 必要补充:平台线程与虚拟线程

在传统 Java 教程中,经常直接把:

Java Thread
≈
操作系统线程

进行理解。

对于传统平台线程(Platform Thread),这种理解在入门阶段基本可用。

但是在 JDK 21 中,虚拟线程(Virtual Thread)已经成为正式特性。

因此更准确的现代模型是:

Java Thread
├── Platform Thread
└── Virtual Thread

平台线程

平台线程通常由操作系统负责调度,对应传统 Java 多线程学习的主要模型。

本课程接下来通过:

class MyThread extends Thread

创建的就是传统平台线程方向。

虚拟线程

虚拟线程主要由 JVM 调度,并会在平台线程上执行。

因此不能再笼统地说:

“所有 Java Thread 都由操作系统直接调度。”

更准确的说法是:

平台线程
    ↓
通常由操作系统调度

虚拟线程
    ↓
由 JVM 调度
    ↓
在平台线程上执行

本阶段首先掌握经典平台线程、线程安全、线程池等 JavaSE 基础。

虚拟线程属于 JDK 21 很重要的现代并发能力,但不改变我们现在学习的:

进程
线程
并发
并行
任务
共享资源

这些基本概念。


五、实践应用

5.1 Web 服务

一个网站可能同时收到:

请求 A:登录
请求 B:查询文章
请求 C:发表评论
请求 D:上传头像

如果请求全部严格串行处理,服务器吞吐能力会非常有限。

因此服务端开发天然是并发编程的重要领域。


5.2 即时通信系统

后面的局域网即时通信项目会出现:

客户端 A
客户端 B
客户端 C
        ↓
      服务端

服务端不能只服务一个用户。

它必须处理:

多个客户端连接
多个消息
多个 I/O 操作

因此网络编程和多线程会自然组合。


5.3 文件下载

下载程序可能同时:

线程 A → 下载
线程 B → 更新进度
线程 C → 处理用户操作

比所有职责放在一个执行流程中更加合理。


5.4 后台任务

例如:

用户提交订单
       ↓
立即返回订单结果

同时后台:
├── 写日志
├── 发送消息
└── 执行其他任务

这类系统通常也会涉及异步和并发执行。


六、常见问题

6.1 一个程序就是一个线程吗?

不是。

程序运行后形成进程,一个进程内部可以存在多个线程。


6.2 一个 Java 程序只有 main 线程吗?

不是。

从学习者编写业务代码的角度,前期通常只主动使用 main 线程。

但 JVM 本身还可能存在其他运行时线程。

后续我们还会主动创建更多业务线程。


6.3 并发就是并行吗?

不是。

并发强调:

多个任务在重叠时间范围内推进

并行强调:

多个任务在同一瞬间真正执行

6.4 单核 CPU 能不能并发?

可以。

CPU 可以快速在多个线程之间切换。


6.5 单核 CPU 能不能真正同时执行多个 CPU 指令流?

从单个核心的普通执行模型理解,不能同时执行多个独立线程的普通指令流。

它主要通过快速切换制造宏观上的并发效果。


6.6 多核 CPU 就一定是并行吗?

不一定。

是否真正发生并行还受:

  • 可运行线程数量;
  • 调度;
  • 程序结构;
  • 系统负载;

等因素影响。


6.7 线程越多是不是程序越快?

不是。

线程过多甚至可能因为:

调度
上下文切换
内存
锁竞争

导致性能下降。

后面学习线程池时会专门解决:

线程不是越多越好,应该合理管理线程资源。


6.8 并发为什么容易出 Bug?

因为多个线程可能:

同时读取
同时修改
同一份共享数据

最终执行顺序又具有不确定性。

于是程序结果可能依赖执行时机。

这就是后续“线程安全”的核心问题。


七、练习与验收

7.1 知识问答

  1. 什么是进程?什么是线程?
  2. 进程和线程之间是什么关系?
  3. 为什么说线程是程序中的一条执行流程?
  4. Java 程序中的 main 线程是什么?
  5. 什么叫单线程程序?
  6. 什么叫多线程程序?
  7. 什么是并发?
  8. 什么是并行?
  9. 并发与并行最核心的区别是什么?
  10. 单核 CPU 为什么也能实现并发?
  11. 多核 CPU 为什么能够实现真正的并行?
  12. 为什么并发和并行可以同时存在?
  13. 多线程主要可以解决哪些类型的问题?
  14. 为什么多线程不一定让程序更快?
  15. 多线程会引入哪些新的工程问题?
  16. JDK 21 中平台线程与虚拟线程最基本的区别是什么?

7.2 代码阅读

阅读下面的伪执行模型:

任务 A:读取文件 5 秒
任务 B:计算数据 2 秒
任务 C:发送网络请求 3 秒

回答:

  1. 如果三个任务严格串行执行,大致是什么执行顺序?
  2. 哪些任务可能存在等待?
  3. 如果使用多个线程,哪些任务可以在时间上重叠?
  4. 多线程一定可以把总耗时变成 5 秒吗?为什么?

7.3 手写代码

暂时不要创建新线程。

请分别写出三个普通 Java 方法:

download()
calculate()
writeLog()

然后:

  1. main() 中依次调用三个方法。
  2. 画出当前程序唯一的主要执行流程。
  3. 思考如果 download() 阻塞 10 秒,后两个方法什么时候才能执行。
  4. 写出如果未来拆成多个线程后,你希望得到怎样的执行模型。

7.4 Debug

某同学说:

“我的电脑是 8 核,所以只要写 100 个线程,这 100 个线程就会同时运行。”

请指出其中至少三个概念问题。


另一位同学说:

“并发就是 CPU 一会执行 A、一会执行 B,所以多核 CPU 不存在并发,只有并行。”

判断这句话是否正确,并说明原因。


7.5 综合训练

设计一个“即时通信服务器”的概念模型。

服务器当前连接:

用户 A
用户 B
用户 C
用户 D

请回答:

  1. 如果服务端只有一条业务执行流程,可能出现什么问题?
  2. 哪些任务天然适合并发处理?
  3. 哪些数据可能被多个线程共享?
  4. 共享数据会引出什么新的风险?
  5. 为什么后面需要学习线程安全和线程池?

本题暂时不要求编码。


7.6 本章验收

如果能够在不查看资料的情况下完成以下任务,可以认为本章基本掌握:

  • 画出 操作系统 → 进程 → 线程 的关系图。
  • 用自己的语言解释进程与线程。
  • 用一个生活案例解释并发。
  • 用一个生活案例解释并行。
  • 明确说明并发不等于并行。
  • 解释为什么多线程可以提高响应能力。
  • 解释为什么线程不是越多越好。
  • 解释为什么多个线程访问共享资源会产生新的风险。
  • 知道 JDK 21 中除了传统平台线程之外还有虚拟线程。