进程与线程:计算机里的「房子」和「房间」
凌晨 2 点,你的程序突然卡住了。你打开任务管理器,发现内存占用 2GB——这是「进程」在呼吸。而旁边那个 CPU 占用 100% 的条目,是某个「线程」在疯狂运算。
进程和线程,是 Java 并发编程的地基。你可能每天都在用,但真的理解它们的区别吗?
进程:资源分配的「房子」
进程(Process) 是操作系统分配资源的基本单位。
想象进程是一栋独立的房子:
- 每栋房子有自己独立的「房产证」(地址空间)
- 房子里的家具(内存)只能自己用,别人进不来
- 房子之间说话需要打电话(进程通信)
java
// Java 中,每个程序运行都对应一个进程
// 通过 Runtime 或 ProcessBuilder 可以创建新进程
ProcessBuilder pb = new ProcessBuilder("notepad.exe");
Process p = pb.start();进程的核心特征
| 特征 | 说明 |
|---|---|
| 独立性 | 每个进程有独立的虚拟地址空间 |
| 资源拥有 | 持有 CPU、内存、文件句柄、I/O 设备等资源 |
| 开销大 | 创建、切换需要操作系统介入,用户态→内核态切换 |
| 通信复杂 | 需要 IPC(管道、消息队列、Socket 等) |
线程:CPU 调度的「房间」
线程(Thread) 是 CPU 调度的最小单位。
如果说进程是一栋房子,线程就是房子里的房间:
- 所有房间共享房子的基础设施(厨房、卫生间)
- 每个房间有自己独立的「床铺」(程序计数器)和「工作台」(栈)
- 房间之间说话不用打电话,直接喊就行
java
// Java 中,线程是最基本的调度单位
Thread t = new Thread(() -> {
// 这个代码运行在一个独立的线程中
System.out.println("我是线程,我有自己的执行流");
});
t.start();线程的核心特征
| 特征 | 说明 |
|---|---|
| 共享资源 | 共享进程的堆内存、文件句柄、静态变量 |
| 独立资源 | 独立的程序计数器、虚拟机栈、本地方法栈 |
| 开销小 | 创建/切换开销远小于进程(无需切换地址空间) |
| 通信简单 | 直接通过共享内存通信(需要同步) |
Java 线程的内存结构
这是面试常考的知识点。Java 线程在 JVM 中的内存布局:
┌─────────────────────────────────────────────────────────┐
│ 进程内存空间 │
│ ┌───────────────────────────────────────────────────┐ │
│ │ 堆内存 (Heap) │ │
│ │ - 所有线程共享 │ │
│ │ - 对象实例、数组 │ │
│ └───────────────────────────────────────────────────┘ │
│ ┌───────────────────────────────────────────────────┐ │
│ │ 方法区 (Method Area) │ │
│ │ - 所有线程共享 │ │
│ │ - 类信息、常量、静态变量 │ │
│ └───────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ 线程 1 栈 │ │ 线程 2 栈 │ │ 线程 3 栈 │ │
│ │ PC Register│ │ PC Register│ │ PC Register│ │
│ │ JVM Stack │ │ JVM Stack │ │ JVM Stack │ │
│ │ Native Stack│ │ Native Stack│ │ Native Stack│ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
└─────────────────────────────────────────────────────────┘每个线程独有的区域
- 程序计数器(PC Register):记录当前执行的字节码行号,线程切换时恢复执行位置
- 虚拟机栈(VM Stack):存储局部变量、方法调用栈帧
- 本地方法栈(Native Stack):native 方法的调用栈
线程共享的区域
- 堆内存:所有对象实例和数组
- 方法区:类的结构信息、静态变量、字符串常量池
进程 vs 线程:对比一览
| 对比维度 | 进程 | 线程 |
|---|---|---|
| 定义 | 资源分配的基本单位 | CPU 调度的基本单位 |
| 地址空间 | 独立 | 共享 |
| 通信方式 | IPC(复杂) | 直接共享(需同步) |
| 创建开销 | 大(MB 级) | 小(KB 级) |
| 切换开销 | 大(用户态↔内核态) | 小(寄存器刷新) |
| 安全性 | 隔离,并发问题少 | 共享,并发问题多 |
| 独立性 | 完全独立 | 依赖进程存在 |
为什么 Java 使用线程而不是进程?
- 性能优先:线程切换开销远小于进程,适合高并发场景
- 资源共享:多线程可以方便地共享进程资源
- 响应更快:一个线程阻塞不影响其他线程
- 模型天然契合:Java 程序通常是多任务的(UI、网络、计算),线程是最自然的抽象
实际应用场景
进程的使用场景
- 隔离构建:Maven/Gradle 每次构建是独立的进程
- 外部程序调用:调用系统命令、第三方程序
- 服务隔离:微服务架构中不同服务是独立进程
线程的使用场景
- Web 服务器:Tomcat 用线程池处理并发请求
- 异步处理:发送邮件、记录日志可以放到独立线程
- 并行计算:大数据处理、图像处理分片并行
- GUI 事件处理:Swing/Android 中 UI 线程和后台线程分离
java
// Web 服务器的线程模型示例
public class SimpleServer {
public static void main(String[] args) throws IOException {
ServerSocket ss = new ServerSocket(8080);
while (true) {
// 每收到一个请求,就创建一个线程处理
Socket socket = ss.accept();
new Thread(() -> handleRequest(socket)).start();
}
}
}留给你的思考题
进程和线程的根本区别是什么?
如果有两个线程同时修改同一个静态变量 static int counter = 0,会发生什么?
这种「同时修改」的问题,有一个专有名词,叫什么?
面试追问方向:
- 进程间通信有哪些方式?各自优缺点是什么?
- 线程切换为什么比进程切换快?
- Java 中如何获取当前线程?
Thread.currentThread()返回的是什么?
