操作系统基础知识梳理
操作系统基础知识梳理
作者:没有四次元口袋的蓝胖
日期:2026-08-04
标签:Java, 进程, 线程, 死锁, 操作系统
操作系统基础是 Java 面试的常考内容。核心掌握:进程与线程的区别、死锁的四个必要条件、如何避免死锁。
一、进程与线程
1.1 基本定义
进程(Process):
- 程序的一次执行过程
- 操作系统资源分配的基本单位
- 拥有独立的内存空间
线程(Thread):
- 进程内的执行单元
- CPU 调度的基本单位
- 共享进程的内存空间
进程 A(独立内存空间)
├── 线程 1(共享进程 A 的资源)
├── 线程 2(共享进程 A 的资源)
└── 线程 3(共享进程 A 的资源)
进程 B(独立内存空间)
├── 线程 1(共享进程 B 的资源)
└── 线程 2(共享进程 B 的资源)
1.2 对比表
| 维度 | 进程 | 线程 |
|---|---|---|
| 定义 | 程序执行实例 | 进程内的执行单元 |
| 资源分配 | 独立内存空间 | 共享进程资源 |
| CPU 调度 | 不是基本单位 | 基本单位 |
| 内存 | 独立 | 共享堆和方法区,私有栈 |
| 创建销毁 | 开销大 | 开销小 |
| 切换 | 开销大(需切换内存空间) | 开销小(共享内存) |
| 通信 | 需要 IPC(管道、消息队列、共享内存) | 直接访问共享变量 |
| 安全性 | 一个崩溃不影响其他 | 一个崩溃可能导致整个进程崩溃 |
1.3 为什么需要线程?
- 提高并发:多核 CPU 可以同时执行多个线程
- 资源共享:线程共享进程资源,通信方便
- 减少开销:线程切换比进程切换开销小
1.4 线程共享与私有
| 区域 | 共享/私有 | 说明 |
|---|---|---|
| 堆 | 共享 | 对象实例,需要同步控制 |
| 方法区 | 共享 | 类信息、静态变量 |
| 虚拟机栈 | 私有 | 局部变量、方法调用 |
| 程序计数器 | 私有 | 执行位置 |
面试回答模板:进程是资源分配的基本单位,拥有独立内存空间;线程是 CPU 调度的基本单位,共享进程资源。线程切换开销小,但一个线程崩溃可能导致整个进程崩溃;进程间通信需要 IPC 机制,线程间可以直接访问共享变量。
二、死锁(Deadlock)
2.1 什么是死锁?
多个线程互相持有对方需要的资源,导致所有线程都无法继续执行。
线程 A:持有锁 1,等待锁 2
线程 B:持有锁 2,等待锁 1
→ 两者互相等待,永远无法执行
2.2 死锁代码示例
public class DeadlockExample {
private static final Object lock1 = new Object();
private static final Object lock2 = new Object();
public static void main(String[] args) {
new Thread(() -> {
synchronized (lock1) {
System.out.println("Thread 1: Holding lock 1...");
try { Thread.sleep(100); } catch (Exception e) {}
System.out.println("Thread 1: Waiting for lock 2...");
synchronized (lock2) {
System.out.println("Thread 1: Holding lock 1 & 2...");
}
}
}).start();
new Thread(() -> {
synchronized (lock2) {
System.out.println("Thread 2: Holding lock 2...");
try { Thread.sleep(100); } catch (Exception e) {}
System.out.println("Thread 2: Waiting for lock 1...");
synchronized (lock1) {
System.out.println("Thread 2: Holding lock 1 & 2...");
}
}
}).start();
}
}
2.3 死锁四要素(必要条件)
死锁的发生必须同时满足以下四个条件,破坏任意一个即可避免死锁。
| 条件 | 含义 | 破坏方法 |
|---|---|---|
| 互斥 | 资源同一时刻只能被一个线程持有 | 无法破坏(某些资源必须互斥) |
| 持有并等待 | 线程持有资源的同时请求其他资源 | 一次性申请所有资源 |
| 不可抢占 | 已获得的资源不能被强制剥夺 | 允许抢占(获取失败释放已持有) |
| 循环等待 | 存在线程间的环形等待链 | 按顺序申请资源(破坏环路) |
循环等待示例:
线程 A → 等待 → 线程 B → 等待 → 线程 C → 等待 → 线程 A
↑ │
└──────────────────────────────────────┘
2.4 避免死锁的方法
方法一:按顺序申请资源(破坏循环等待)
// 所有线程先获取 lock1,再获取 lock2
new Thread(() -> {
synchronized (lock1) { // 先获取 lock1
synchronized (lock2) { // 再获取 lock2
// 业务逻辑
}
}
}).start();
new Thread(() -> {
synchronized (lock1) { // 同样先获取 lock1
synchronized (lock2) { // 再获取 lock2
// 业务逻辑
}
}
}).start();
方法二:一次性申请所有资源(破坏持有并等待)
// 同时获取所有需要的锁
synchronized (lock1) {
// 或者用 ReentrantLock 的 tryLock
if (lock2.tryLock()) {
try {
// 业务逻辑
} finally {
lock2.unlock();
}
}
}
方法三:使用超时机制(破坏不可抢占)
ReentrantLock lock1 = new ReentrantLock();
ReentrantLock lock2 = new ReentrantLock();
public void doSomething() throws InterruptedException {
if (lock1.tryLock(1, TimeUnit.SECONDS)) {
try {
if (lock2.tryLock(1, TimeUnit.SECONDS)) {
try {
// 业务逻辑
} finally {
lock2.unlock();
}
} else {
System.out.println("获取 lock2 超时,释放 lock1");
}
} finally {
lock1.unlock();
}
}
}
方法四:避免嵌套锁
最简单的办法:不要在一个锁内获取另一个锁。
三、死锁检测与排查
3.1 jstack 检测死锁
# 获取 Java 进程 PID
jps
# 查看线程堆栈
jstack <pid>
死锁输出示例:
Found one Java-level deadlock:
=============================
"Thread-1":
waiting to lock monitor 0x00007f8b2c003828 (object 0x000000076ab5e6e0, a java.lang.Object),
which is held by "Thread-0"
"Thread-0":
waiting to lock monitor 0x00007f8b2c0061b8 (object 0x000000076ab5e6f0, a java.lang.Object),
which is held by "Thread-1"
3.2 死锁排查工具
| 工具 | 用途 |
|---|---|
| jstack | 打印线程堆栈,检测死锁 |
| JConsole | 可视化监控,检测死锁 |
| VisualVM | 功能更全面,支持死锁检测 |
| Arthas | 阿里开源,功能强大 |
四、进程间通信(IPC)
4.1 常见方式
| 方式 | 特点 | 适用场景 |
|---|---|---|
| 管道(Pipe) | 半双工,只能单向传输 | 父子进程通信 |
| 命名管道(FIFO) | 半双工,允许无关系进程通信 | 无关系进程通信 |
| 消息队列 | 消息链表,存储在内存中 | 消息传递 |
| 共享内存 | 最快,直接读写共享区域 | 大数据量传输 |
| 信号量 | 计数器,用于同步 | 进程同步 |
| 信号(Signal) | 异步通知 | 异常处理 |
| 套接字(Socket) | 支持网络,跨机器通信 | 网络通信 |
4.2 线程间通信
Java 中线程间通信常用方式:
// 1. wait() / notify() / notifyAll()
synchronized (obj) {
while (condition) {
obj.wait(); // 等待
}
obj.notifyAll(); // 唤醒
}
// 2. Condition(ReentrantLock)
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
lock.lock();
try {
condition.await(); // 等待
condition.signal(); // 唤醒
} finally {
lock.unlock();
}
// 3. volatile(可见性)
volatile boolean flag = false;
// 4. 并发工具类
CountDownLatch / CyclicBarrier / Semaphore
🗺️ 思维导图速览
进程与线程、死锁
├── 进程 vs 线程
│ ├── 进程:资源分配单位,独立内存
│ ├── 线程:CPU 调度单位,共享进程资源
│ └── 区别:创建开销、切换开销、通信方式
├── 死锁
│ ├── 定义:互相持有对方需要的资源
│ ├── 四要素
│ │ ├── 互斥(无法破坏)
│ │ ├── 持有并等待(一次性申请)
│ │ ├── 不可抢占(允许抢占/超时)
│ │ └── 循环等待(按顺序申请)
│ └── 避免方法
│ ├── 按顺序申请资源
│ ├── 一次性申请所有资源
│ └── 使用超时机制
├── 死锁检测
│ └── jstack、JConsole、Arthas
└── 进程间通信(IPC)
├── 管道、消息队列、共享内存
└── 信号量、信号、套接字
📝 写在最后
学习建议
- 进程 vs 线程要对比记忆:资源分配 vs CPU 调度、独立内存 vs 共享内存
- 死锁四要素要记牢:互斥、持有并等待、不可抢占、循环等待,破坏任意一个即可避免
- 避免死锁的方法要知道:按顺序申请、一次性申请、超时机制
- 死锁检测工具要会用:jstack 是最基本的,面试可能问"如何排查死锁"
面试回答模板
Q:进程和线程的区别?
进程是资源分配的基本单位,拥有独立的内存空间;线程是 CPU 调度的基本单位,共享进程的堆和方法区,但栈和程序计数器私有。线程创建和切换开销比进程小,但一个线程崩溃可能导致整个进程崩溃;进程间通信需要 IPC 机制(管道、消息队列、共享内存),线程间可以直接访问共享变量。
Q:什么是死锁?死锁的四个必要条件?
死锁是多个线程互相持有对方需要的资源,导致所有线程都无法执行。四个必要条件:互斥(资源只能被一个线程持有)、持有并等待(线程持有资源的同时请求其他资源)、不可抢占(已获得的资源不能被强制剥夺)、循环等待(存在线程间的环形等待链)。破坏任意一个条件即可避免死锁。
Q:如何避免死锁?
破坏四个必要条件之一即可。常用方法:按顺序申请资源(破坏循环等待)、一次性申请所有资源(破坏持有并等待)、使用超时机制(破坏不可抢占)、避免嵌套锁。实际开发中,按顺序申请资源是最简单有效的方法。
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐
所有评论(0)