线程与进程
进程和线程是操作系统调度程序执行的基础概念。Java 并发最终运行在操作系统线程之上,因此理解进程、线程、上下文切换和资源隔离非常重要。
# 1. 进程
进程是程序的一次运行实例。每个进程通常拥有独立的地址空间和系统资源。
进程 A
├─ 代码段
├─ 堆
├─ 栈
├─ 文件描述符
└─ 线程 A1 / A2
进程 B
├─ 独立地址空间
└─ 线程 B1 / B2
进程之间默认隔离,一个进程崩溃通常不会直接破坏另一个进程的内存。
# 2. 线程
线程是进程中的执行单元。同一个进程内的多个线程共享进程资源,但每个线程有自己的执行栈和程序计数器。
Java 进程
├─ 堆:所有线程共享
├─ 方法区/元空间:所有线程共享
├─ 文件句柄:进程级资源
├─ 线程 1:栈、程序计数器
├─ 线程 2:栈、程序计数器
└─ 线程 3:栈、程序计数器
共享带来高效协作,也带来线程安全问题。
# 3. 进程与线程对比
| 对比项 | 进程 | 线程 |
|---|---|---|
| 资源隔离 | 强 | 同进程内共享 |
| 创建成本 | 高 | 较低 |
| 切换成本 | 高 | 较低 |
| 通信方式 | IPC、网络、文件、管道等 | 共享内存、锁、队列等 |
| 崩溃影响 | 通常影响自身进程 | 可能影响整个进程 |
| Java 对应 | JVM 进程 | Java Thread |
Java 中一个应用通常就是一个 JVM 进程,进程里包含多个 Java 线程和 JVM 自身后台线程。
# 4. 用户线程与守护线程
Java 线程分为用户线程和守护线程。
Thread thread = new Thread(task);
thread.setDaemon(true);
thread.start();
区别:
| 类型 | 说明 |
|---|---|
| 用户线程 | JVM 会等待它执行结束 |
| 守护线程 | JVM 不会因为它继续存活而阻止退出 |
典型守护线程:
- GC 相关线程。
- 监控或后台清理线程。
注意:守护线程不适合执行必须完成的业务任务,因为 JVM 退出时它可能被直接终止。
# 5. 上下文切换
CPU 在多个线程之间切换执行,需要保存和恢复线程上下文。
线程 A 运行
│ 保存寄存器、程序计数器、栈状态
▼
线程 B 运行
│ 保存/恢复上下文
▼
线程 A 继续运行
上下文切换成本包括:
- 调度器开销。
- CPU 缓存失效。
- 寄存器状态保存恢复。
- 线程从运行态切到阻塞态再恢复的延迟。
线程过多时,系统可能花大量时间调度线程,而不是执行业务代码。
# 6. 线程共享了什么
同一 JVM 进程内线程共享:
- 堆对象。
- 类元数据。
- 静态变量。
- 打开的文件和网络连接。
- 进程环境。
线程独有:
- Java 虚拟机栈。
- 本地方法栈。
- 程序计数器。
- ThreadLocal 中属于该线程的值。
共享资源访问不当会产生并发问题。
# 7. Java 线程与操作系统线程
现代 HotSpot JVM 中,Java 平台线程通常与操作系统线程一一对应。
Java Thread
│
▼
OS Thread
│
▼
CPU 调度
这意味着:
- 创建平台线程有操作系统成本。
- 阻塞平台线程会占用 OS 线程资源。
- 线程数量不能无限增加。
Java 21 引入的虚拟线程改变的是 Java 任务到平台线程的承载方式,虚拟线程数量可以远多于平台线程,但它仍然需要最终运行在平台线程上。
# 8. 多线程一定更快吗
不一定。
多线程更适合:
- 任务可以拆分。
- 子任务相互独立。
- 存在 IO 等待。
- 多核 CPU 可利用。
多线程可能更慢:
- 任务很小,线程调度成本超过收益。
- 共享数据竞争严重。
- 锁粒度过大。
- 下游资源成为瓶颈。
- 线程数量远超系统承载能力。
# Tips 快问快答
Q:进程和线程最大的区别是什么? A:进程是资源分配单位,线程是执行调度单位,同一进程内线程共享内存。
Q:线程共享堆吗? A:同一 JVM 进程中的线程共享堆。
Q:每个线程都有自己的栈吗? A:有。每个线程有自己的虚拟机栈、程序计数器等运行上下文。
Q:守护线程适合做核心业务吗? A:不适合,JVM 退出时守护线程可能来不及完成任务。
Q:线程切换为什么有成本? A:需要保存和恢复上下文,还可能导致 CPU 缓存失效。
Q:Java Thread 是不是操作系统线程? A:平台线程通常对应操作系统线程;虚拟线程是更轻量的 Java 层线程。
Q:为什么线程太多会变慢? A:上下文切换、锁竞争、内存占用和调度成本都会上升。
Q:多进程比多线程安全吗? A:隔离性更强,但通信成本更高,不能简单说谁一定更好。