操作系统基础知识梳理

作者:没有四次元口袋的蓝胖
日期: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 为什么需要线程?

  1. 提高并发:多核 CPU 可以同时执行多个线程
  2. 资源共享:线程共享进程资源,通信方便
  3. 减少开销:线程切换比进程切换开销小

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)
    ├── 管道、消息队列、共享内存
    └── 信号量、信号、套接字

📝 写在最后

学习建议

  1. 进程 vs 线程要对比记忆:资源分配 vs CPU 调度、独立内存 vs 共享内存
  2. 死锁四要素要记牢:互斥、持有并等待、不可抢占、循环等待,破坏任意一个即可避免
  3. 避免死锁的方法要知道:按顺序申请、一次性申请、超时机制
  4. 死锁检测工具要会用:jstack 是最基本的,面试可能问"如何排查死锁"

面试回答模板

Q:进程和线程的区别?

进程是资源分配的基本单位,拥有独立的内存空间;线程是 CPU 调度的基本单位,共享进程的堆和方法区,但栈和程序计数器私有。线程创建和切换开销比进程小,但一个线程崩溃可能导致整个进程崩溃;进程间通信需要 IPC 机制(管道、消息队列、共享内存),线程间可以直接访问共享变量。

Q:什么是死锁?死锁的四个必要条件?

死锁是多个线程互相持有对方需要的资源,导致所有线程都无法执行。四个必要条件:互斥(资源只能被一个线程持有)、持有并等待(线程持有资源的同时请求其他资源)、不可抢占(已获得的资源不能被强制剥夺)、循环等待(存在线程间的环形等待链)。破坏任意一个条件即可避免死锁。

Q:如何避免死锁?

破坏四个必要条件之一即可。常用方法:按顺序申请资源(破坏循环等待)、一次性申请所有资源(破坏持有并等待)、使用超时机制(破坏不可抢占)、避免嵌套锁。实际开发中,按顺序申请资源是最简单有效的方法。

Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐