Java 多线程面试核心知识点总结
整理了Java多线程面试中最常考的知识点,包含详细代码解释和图解
1. 进程与线程的概念
1.1 什么是进程?
进程是操作系统进行资源分配和调度的基本单位,是正在运行的程序的实例。
进程的核心特征
| 特征 |
说明 |
| 独立性 |
每个进程拥有独立的地址空间、文件描述符、寄存器状态 |
| 资源拥有 |
操作系统分配给进程:内存、文件句柄、I/O设备 |
| 生命周期 |
创建 → 就绪 → 运行 → 阻塞 → 终止 |
| 通信方式 |
进程间通信(IPC):管道、消息队列、共享内存、socket等 |
进程的结构
1 2 3 4 5 6 7 8 9 10 11
| 进程 ├── PCB (进程控制块) │ ├── 进程ID (PID) │ ├── 进程状态 │ ├── 寄存器上下文 │ ├── 内存管理信息 │ └── 资源限制 ├── 代码段 (Text) ├── 数据段 (Data) ├── 堆 (Heap) └── 栈 (Stack)
|
进程与程序的区别
| 程序 |
进程 |
| 静态的磁盘文件 |
动态的内存实例 |
| 永久保存 |
有生命周期 |
| 被动实体 |
主动实体 |
1.2 什么是线程?
线程是进程内的执行单元,是CPU调度的最小单位。
线程的核心特征
| 特征 |
说明 |
| 轻量级 |
线程共享进程的地址空间,创建/切换成本远低于进程 |
| 共享资源 |
同一进程内的线程共享:代码段、数据段、堆、打开的文件 |
| 独立资源 |
每个线程独有:栈、寄存器、程序计数器、线程局部存储 |
| 并发执行 |
同一进程内多线程可并行执行在多核CPU上 |
2. 线程与进程的关系、区别及优缺点
2.1 关系
1 2 3 4
| 进程 (资源分配单位) └── 线程1 (CPU调度单位) └── 线程2 (CPU调度单位) └── 线程3 (CPU调度单位)
|
一个进程可以包含多个线程,线程是进程内的执行流,共享进程的资源。
2.2 核心区别
| 维度 |
进程 |
线程 |
| 根本区别 |
资源分配的基本单位 |
CPU调度的基本单位 |
| 地址空间 |
独立(隔离) |
共享(同一进程内) |
| 创建/销毁 |
慢 (ms级) |
快 (μs级) |
| 切换开销 |
大(切换页表、缓存) |
小(仅切换寄存器和栈) |
| 通信 |
需 IPC(复杂) |
直接共享内存(简单) |
| 安全性 |
高(独立地址空间) |
低(共享地址空间) |
| 容错性 |
高(一个进程挂不影响其他) |
低(线程崩溃可能波及整个进程) |
2.3 进程优缺点
✅ 优点:
- 独立地址空间,隔离性好,一个进程崩溃不影响其他
- 适合 CPU 密集型任务,可充分利用多核
- 编程简单(无需考虑同步)
❌ 缺点:
- 创建/切换开销大
- 进程间通信复杂(需要 IPC)
- 内存占用高
2.4 线程优缺点
✅ 优点:
- 轻量级,创建/切换快
- 共享进程资源,通信简单(直接读写共享内存)
- 适合 I/O 密集型任务
❌ 缺点:
- 共享地址空间,需要同步(加锁),编程复杂
- 一个线程崩溃可能导致整个进程崩溃
- 受 GIL 限制(Python 中,Java无此问题)
2.5 使用场景
| 场景 |
推荐 |
| CPU 密集型任务 |
多进程 |
| I/O 密集型任务 |
多线程 |
| 需要高隔离性 |
多进程 |
| 需要频繁通信 |
多线程 |
| Python CPU 密集 |
multiprocessing |
3. 如何创建线程 (Java)
3.1 方式一:继承 Thread 类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| class MyThread extends Thread { @Override public void run() { System.out.println("Thread is running!"); } }
public class CreateThreadDemo { public static void main(String[] args) { MyThread thread = new MyThread(); thread.start(); } }
|
代码解释:
extends Thread:让 MyThread 成为一个线程类
run():线程执行的主体方法,写在线程中要做的任务
start():启动线程的唯一正确方式,会创建新线程并调用 run()
3.2 方式二:实现 Runnable 接口(推荐)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| class MyRunnable implements Runnable { @Override public void run() { System.out.println("Runnable thread is running!"); } }
public class CreateThreadDemo2 { public static void main(String[] args) { MyRunnable runnable = new MyRunnable(); Thread thread = new Thread(runnable); thread.start(); new Thread(() -> { System.out.println("Lambda thread is running!"); }).start(); } }
|
代码解释:
implements Runnable:实现接口,比继承 Thread 更好(Java 不支持多继承)
new Thread(runnable):将任务对象传给 Thread,Thread 负责创建线程
() -> {}:Lambda 表达式,简化匿名内部类写法
3.3 方式三:使用线程池(推荐)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors;
public class ThreadPoolDemo { public static void main(String[] args) { ExecutorService executor = Executors.newFixedThreadPool(5); for (int i = 0; i < 10; i++) { final int taskId = i; executor.submit(() -> { System.out.println("Task " + taskId + " is running"); }); } executor.shutdown(); } }
|
代码解释:
ExecutorService:线程池接口,管理线程的生命周期
Executors.newFixedThreadPool(5):创建固定5个线程的线程池
executor.submit():提交任务给线程池执行
executor.shutdown():优雅关闭线程池,不再接收新任务
3.4 方式四:实现 Callable 接口 + Future
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| import java.util.concurrent.Callable; import java.util.concurrent.FutureTask; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors;
public class CallableDemo { public static void main(String[] args) throws Exception { Callable<Integer> callable = () -> { int sum = 0; for (int i = 1; i <= 100; i++) { sum += i; } return sum; }; FutureTask<Integer> futureTask = new FutureTask<>(callable); Thread thread = new Thread(futureTask); thread.start(); Integer result = futureTask.get(); System.out.println("Result: " + result); } }
|
代码解释:
Callable<T>:带返回值的任务接口,泛型指定返回值类型
FutureTask:包装 Callable,可以像 Runnable 一样交给线程执行
futureTask.get():获取任务返回值,如果任务未完成会阻塞等待
4. 线程的生命周期和状态
4.1 线程的6种状态
Java 中线程有 6 种状态(Thread.State 枚举):

1 2 3
| NEW → RUNNABLE → BLOCKED/WAITING/TIMED_WAITING → TERMINATED ↓ (可再次回到 RUNNABLE)
|
| 状态 |
说明 |
触发方式 |
| NEW |
线程创建但未启动 |
new Thread() |
| RUNNABLE |
可运行(就绪+运行) |
start() |
| BLOCKED |
阻塞(等待获取锁) |
synchronized 未获取锁 |
| WAITING |
无限等待 |
Object.wait(), Thread.join(), LockSupport.park() |
| TIMED_WAITING |
限时等待 |
Thread.sleep(), wait(timeout), join(timeout) |
| TERMINATED |
已终止 |
run() 执行完毕 |
4.2 状态转换图
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| ┌──────────────────┐ │ NEW │ └────────┬─────────┘ │ start() ▼ ┌──────────────────┐ ┌────────▶│ RUNNABLE │◀──────────┐ │ └────────┬─────────┘ │ │ │ │ │ │ I/O/锁/sleep │ │ ▼ │ │ ┌──────────────────┐ │ │ │ BLOCKED │ │ │ │ /WAITING │ │ │ └────────┬─────────┘ │ │ │ notify/wimer/锁获得 │ │ │ │ └──────────────────┘ │ run() 结束 │ ▼ ┌──────────────────┐ │ TERMINATED │ └──────────────────┘
|
4.3 各状态代码示例
NEW(新建)
1 2 3 4
| Thread thread = new Thread(() -> System.out.println("Hello"));
System.out.println(thread.getState());
|
代码解释:
new Thread():仅创建了线程对象,尚未分配系统资源
getState():获取线程当前状态
RUNNABLE(可运行)
1 2 3 4 5 6 7 8 9
| Thread thread = new Thread(() -> { for (int i = 0; i < 100000; i++) { Math.random(); } });
thread.start();
System.out.println(thread.getState());
|
代码解释:
start():启动线程,线程状态变为 RUNNABLE
- RUNNABLE 包含”就绪(READY)”和”运行(RUNNING)”两种子状态
BLOCKED(阻塞)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| public class BlockedDemo { private static final Object lock = new Object(); public static void main(String[] args) throws InterruptedException { Thread thread1 = new Thread(() -> { synchronized (lock) { try { Thread.sleep(5000); } catch (InterruptedException e) {} } }); Thread thread2 = new Thread(() -> { synchronized (lock) { System.out.println("Thread2 got the lock"); } }); thread1.start(); Thread.sleep(100); thread2.start(); Thread.sleep(100); System.out.println("Thread2 state: " + thread2.getState()); } }
|
代码解释:
synchronized (lock):尝试获取对象的锁
- 如果锁被其他线程持有,当前线程进入 BLOCKED 状态等待获取锁
- BLOCKED 状态的线程不占用 CPU
WAITING(无限等待)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| public class WaitingDemo { private static final Object lock = new Object(); public static void main(String[] args) throws InterruptedException { Thread thread = new Thread(() -> { synchronized (lock) { try { lock.wait(); } catch (InterruptedException e) {} } }); thread.start(); Thread.sleep(100); System.out.println("Thread state: " + thread.getState()); } }
|
代码解释:
lock.wait():让当前线程在 lock 的等待集中无限等待
- wait() 会释放当前持有的锁
- 必须先获取 lock 的锁才能调用 wait()
TIMED_WAITING(限时等待)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| public class TimedWaitingDemo { public static void main(String[] args) throws InterruptedException { Thread thread = new Thread(() -> { try { Thread.sleep(3000); } catch (InterruptedException e) {} }); thread.start(); Thread.sleep(100); System.out.println("Thread state: " + thread.getState()); } }
|
代码解释:
Thread.sleep(3000):让线程休眠3秒
- 限时等待状态下,线程不占用 CPU
- 时间到达后自动恢复到 RUNNABLE 状态
TERMINATED(终止)
1 2 3 4 5 6 7 8 9 10 11 12 13
| public class TerminatedDemo { public static void main(String[] args) throws InterruptedException { Thread thread = new Thread(() -> { System.out.println("Task completed"); }); thread.start(); thread.join(); System.out.println("Thread state: " + thread.getState()); } }
|
代码解释:
join():等待线程执行完毕
- 当 run() 方法执行完毕后,线程状态变为 TERMINATED
5. 线程上下文切换
5.1 什么是线程上下文切换?
线程上下文切换(Context Switch)是指CPU从执行一个线程切换到执行另一个线程的过程。
CPU在同一时刻只能执行一个线程,当需要切换时:
- 保存当前线程的执行状态(寄存器、程序计数器、栈指针等)
- 恢复另一个线程的执行状态
- 切换到新线程执行
5.2 切换的时机
| 触发条件 |
说明 |
| 时间片用完 |
CPU时间片轮转调度 |
| 阻塞 |
线程等待I/O、锁、资源 |
| 优先级 |
高优先级线程抢占 |
| 主动让出 |
yield(), sleep(), wait() |
| 中断 |
硬件中断、系统调用 |
5.3 上下文切换的过程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| 线程A运行中 │ │ 时间片耗尽/阻塞 ▼ ┌─────────────────────┐ │ 1. 保存线程A状态 │ │ - PC (程序计数器) │ │ - Registers │ │ - Stack Pointer │ │ - CPU缓存 │ └─────────────────────┘ │ │ 调度器选择线程B ▼ ┌─────────────────────┐ │ 2. 恢复线程B状态 │ │ - 加载B的寄存器 │ │ - 切换栈空间 │ │ - 刷新CPU缓存 │ └─────────────────────┘ │ ▼ 线程B开始运行
|
5.4 如何减少上下文切换?
- 减少线程数量:合理配置线程池大小
- 使用协程/虚拟线程:Java 21 虚拟线程
- 无锁化设计:CAS、Atomic、ConcurrentHashMap
- 减少阻塞:异步I/O、epoll
- 减少锁竞争:读写锁、分离锁
1 2 3 4
| int cores = Runtime.getRuntime().availableProcessors();
ExecutorService pool = Executors.newFixedThreadPool(cores * 2);
|
代码解释:
Runtime.getRuntime().availableProcessors():获取CPU核心数
- I/O密集型任务线程数设为 CPU核心数×2,可以在等待I/O时让更多线程执行
6. 为什么 wait() 方法不定义在 Thread 中?
6.1 核心原因
wait() 是 对象 monitor(监视器) 的组成部分,不是线程的属性。
Java 的并发同步基于 Monitor 机制:
- 每个对象有一把锁(monitor lock)
- 每个对象有一个等待集(wait set)
wait() / notify() / notifyAll() 操作的是对象的等待集
设计思想:谁拥有资源,谁就负责等待通知,而不是线程本身。
6.2 对比:Object vs Thread
| 方法 |
定义位置 |
原因 |
wait() |
Object |
操作对象的 monitor 等待集 |
notify() |
Object |
唤醒对象的等待线程 |
sleep() |
Thread |
让线程暂停执行 |
yield() |
Thread |
让出 CPU 时间片 |
join() |
Thread |
等待线程结束 |
6.3 代码示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| public class WaitDemo { public static void main(String[] args) throws InterruptedException { Object lock = new Object(); Thread t1 = new Thread(() -> { synchronized (lock) { try { System.out.println("Thread1 waiting..."); lock.wait(); System.out.println("Thread1 resumed"); } catch (InterruptedException e) { e.printStackTrace(); } } }); Thread t2 = new Thread(() -> { synchronized (lock) { System.out.println("Thread2 notifying..."); lock.notify(); } }); t1.start(); Thread.sleep(100); t2.start(); } }
|
代码解释:
synchronized (lock):获取 lock 对象的监视器锁
lock.wait():释放锁并在此对象的等待集中等待
lock.notify():唤醒在此对象等待集中等待的一个线程
- wait/notify 必须在 synchronized 内调用,这是 Java 语言规范
6.4 sleep() 和 wait() 的区别
| 区别 |
sleep() |
wait() |
| 所属类 |
Thread |
Object |
| 是否释放锁 |
不释放 |
释放 |
| 唤醒方式 |
自动醒来/ interrupt() |
notify()/notifyAll() |
| 位置要求 |
任意位置 |
synchronized 内 |
| 状态 |
TIMED_WAITING |
WAITING / TIMED_WAITING |
7. 可以直接调用 Thread 类的 run 方法吗?
7.1 答案:可以,但不推荐
可以直接调用,但它不会启动新线程,而是在当前线程中同步执行。
1 2 3 4 5 6 7
| Thread t = new Thread(() -> System.out.println("Hello"));
t.run();
t.start();
|
7.2 run() vs start() 的区别
| 方法 |
run() |
start() |
| 作用 |
执行线程逻辑 |
启动新线程 |
| 执行方式 |
同步(在当前线程) |
异步(在新线程) |
| 调用次数 |
可以多次调用 |
只能调用一次(否则抛异常) |
| 返回值 |
无 |
无 |
7.3 start() 源码解析
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| public class Thread implements Runnable { private Runnable target; @Override public void run() { if (target != null) { target.run(); } } public synchronized void start() { if (threadStatus != 0) { throw new IllegalThreadStateException(); } start0(); } private native void start0(); }
|
代码解释:
target:存储传入的 Runnable 对象
threadStatus:线程状态,确保 start() 只被调用一次
start0():native 方法,JVM 负责创建操作系统线程
7.4 执行流程对比
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| t.run() 执行流程: 主线程 ──────────────────────► run() 执行完毕 │ └── 直接调用,无新线程
t.start() 执行流程: 主线程 ──────────────────────► start() 返回 │ ▼ ┌───────────────┐ │ JVM 创建新线程 │ │ 调用 start0() │ └───────────────┘ │ ▼ 新线程 ──────────────────────► run() 执行完毕
|
8. 为什么要使用多线程?
8.1 核心原因
充分利用多核 CPU 资源,提升程序性能和响应速度。
8.2 单核时代 vs 多核时代
单核时代:提高 CPU 和 IO 系统整体利用率
1 2 3 4 5 6 7 8 9 10 11
| 单线程情况: 线程 ──────────► [IO阻塞] ──────────► │ │ CPU空闲 CPU空闲
多线程情况: 线程1: ──────────► [IO阻塞] 线程2: ──────────► [使用CPU] 线程3: ──────────► [使用CPU] └─────────────────────────────────► 高 CPU 利用率
|
核心目的:当一个线程被 IO 阻塞时,其他线程可以继续使用 CPU。
多核时代:充分利用多核 CPU 并行计算
1 2 3 4 5 6 7 8 9 10 11 12 13
| 单线程(只使用1个核心): CPU核心1: ██████████████████████ ← 100% 使用 CPU核心2: ░░░░░░░░░░░░░░░░░░░░░ ← 闲置 CPU核心3: ░░░░░░░░░░░░░░░░░░░░░ ← 闲置 CPU核心4: ░░░░░░░░░░░░░░░░░░░░░ ← 闲置
多线程(使用多核): CPU核心1: ██████████████████████ CPU核心2: ██████████████████████ CPU核心3: ██████████████████████ CPU核心4: ██████████████████████
理想加速比: T单核 → T单核/N核心
|
核心目的:将计算任务分配到多个 CPU 核心并行执行。
8.3 多线程的适用场景
| 场景 |
说明 |
| I/O 密集型 |
网络请求、文件读写、数据库操作 |
| 并行计算 |
大数据处理、图像/视频处理 |
| 服务端并发 |
高并发 Web 服务、实时通信 |
| 后台任务 |
日志记录、定时任务、消息推送 |
| UI 交互 |
Android/Web 前端,避免界面卡死 |
9. 单核 CPU 支持 Java 多线程吗?
9.1 答案:支持
Java 多线程不需要多核 CPU,单核 CPU 完全可以运行多线程程序。
9.2 单核 CPU 运行多线程的原理
单核 CPU 实际是 “伪并行”,通过 时间片轮转 实现线程切换:
1 2 3 4 5 6 7 8 9 10 11 12
| 时间线: ┌─────────────────────────────────────────────────────────────┐ │ CPU时间片 │ ├─────────────────────────────────────────────────────────────┤ │ 0ms 10ms 20ms 30ms 40ms 50ms 60ms │ │ ├──┬───┤├──┬───┤├──┬───┤├──┬───┤├──┬───┤├──┬───┤ │ │ │T1│T2 ││T1│T2 ││T1│T2 ││T1│T2 ││T1│T2 ││T1│T2 │ │ │ └──┴───┘└──┴───┘└──┴───┘└──┴───┘└──┴───┘└──┴───┘ │ │ ↑ ↑ ↑ ↑ ↑ ↑ │ │ 切换 切换 切换 切换 切换 切换 │ └─────────────────────────────────────────────────────────────┘ T1 = 线程1 T2 = 线程2
|
- 每个线程轮流使用 CPU 的时间片
- 微观上串行,宏观上并行
- 称为 “并发 (Concurrency)”,不是”并行 (Parallelism)”
9.3 单核 vs 多核
| 维度 |
单核 CPU |
多核 CPU |
| 同时执行线程数 |
1 |
N (核心数) |
| 并行方式 |
并发 (时间片) |
并行 (真正同时) |
| 适合任务 |
I/O 密集型 |
CPU 密集型 |
| 多线程效果 |
避免阻塞 |
真正加速 |
10. 单核 CPU 上运行多个线程效率一定会高吗?
10.1 答案:不一定
多线程并非总是高效,在某些情况下单线程反而更好。
10.2 什么时候多线程效率低?
1. CPU 密集型任务
| 情况 |
单线程 |
多线程 |
| 计算1+2+…+1亿 |
~200ms |
~250ms(含切换开销) |
原因:没有 I/O 阻塞,线程切换只会增加开销,无并行收益。
2. 线程数量过多
1 2 3 4 5
| 线程切换开销: 时间线: [线程1执行][切换][线程2执行][切换][线程3][切换]... ↑ ↑ ↑ 实际工作时间少,切换开销大
|
3. 任务过于简单
1 2 3 4 5
| new Thread(() -> System.out.println("Hi")).start();
|
10.3 什么时候多线程效率高?
| 任务类型 |
单核多线程效果 |
原因 |
| CPU 密集型 |
❌ 更慢 |
线程切换开销 > 并行收益 |
| I/O 密集型 |
✅ 更快 |
等待时切换执行其他任务 |
| 混合型 |
✅ 视情况 |
I/O 占比高则有效 |
| 简单短任务 |
❌ 更慢 |
创建/切换成本太高 |
11. 死锁
11.1 什么是死锁?
死锁是指两个或多个线程相互持有对方需要的资源,导致所有线程都无法继续执行。
1 2 3 4
| 线程A: 持有锁1,等待锁2 线程B: 持有锁2,等待锁1
结果: 无限等待,形成死锁
|
11.2 死锁示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| public class DeadLockDemo { private static final Object lock1 = new Object(); private static final Object lock2 = new Object(); public static void main(String[] args) { Thread thread1 = new Thread(() -> { synchronized (lock1) { System.out.println("Thread1: 持有锁1"); try { Thread.sleep(100); } catch (InterruptedException e) {} synchronized (lock2) { System.out.println("Thread1: 持有锁2"); } } }, "Thread1"); Thread thread2 = new Thread(() -> { synchronized (lock2) { System.out.println("Thread2: 持有锁2"); try { Thread.sleep(100); } catch (InterruptedException e) {} synchronized (lock1) { System.out.println("Thread2: 持有锁1"); } } }, "Thread2"); thread1.start(); thread2.start(); } }
|
代码解释:
synchronized (lock1):获取 lock1 的监视器锁
Thread.sleep(100):模拟业务处理,让另一个线程有足够时间获取另一把锁
- 两个线程互相持有对方需要的锁,形成循环等待 → 死锁
11.3 死锁的四个必要条件 (Coffman 条件)
| 条件 |
说明 |
打破方法 |
| 1. 互斥条件 |
资源一次只能被一个线程持有 |
无法打破 |
| 2. 占有并等待 |
线程持有资源同时等待其他资源 |
一次性获取所有资源 |
| 3. 不可抢占 |
资源不能被强制抢夺 |
设置超时/可抢占 |
| 4. 循环等待 |
线程形成循环等待 |
规定加锁顺序 |
12. 预防和避免线程死锁
12.1 预防死锁:破坏必要条件
1. 破坏”占有并等待”:一次性获取所有资源
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| synchronized (lockA) { synchronized (lockB) { } }
Lock lockA = new ReentrantLock(); Lock lockB = new ReentrantLock();
public void safeMethod() { while (true) { boolean acquiredA = lockA.tryLock(); boolean acquiredB = lockB.tryLock(); if (acquiredA && acquiredB) { try { System.out.println("执行业务逻辑"); } finally { lockA.unlock(); lockB.unlock(); } break; } else { if (acquiredA) lockA.unlock(); if (acquiredB) lockB.unlock(); try { Thread.sleep(10); } catch (InterruptedException e) {} } } }
|
代码解释:
tryLock():尝试获取锁,立即返回(不阻塞)
- 返回 true 表示获取成功,false 表示获取失败
- 循环尝试直到获取成功,或者选择放弃
2. 破坏”不可抢占”:设置超时
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| synchronized (lock) { }
ReentrantLock lock = new ReentrantLock();
public void tryLockMethod() { try { boolean acquired = lock.tryLock(1, TimeUnit.SECONDS); if (acquired) { try { System.out.println("执行业务逻辑"); } finally { lock.unlock(); } } else { System.out.println("获取锁失败,执行其他逻辑"); } } catch (InterruptedException e) { e.printStackTrace(); } }
|
代码解释:
tryLock(timeout, unit):尝试获取锁,最长等待指定时间
- 超时后返回 false,线程可以继续执行其他逻辑
- 配合 finally 确保锁一定会被释放
3. 破坏”循环等待”:固定加锁顺序
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
|
public void transfer(Account from, Account to, int amount) { Account first = from.getId() < to.getId() ? from : to; Account second = from.getId() < to.getId() ? to : from; synchronized (first) { synchronized (second) { from.withdraw(amount); to.deposit(amount); } } }
private void safeMethod(Object lock1, Object lock2) { int hash1 = System.identityHashCode(lock1); int hash2 = System.identityHashCode(lock2); if (hash1 < hash2) { synchronized (lock1) { synchronized (lock2) { } } } else { synchronized (lock2) { synchronized (lock1) { } } } }
|
代码解释:
getId() 或 System.identityHashCode():获取对象的唯一标识
- 根据标识排序,确保所有线程以相同顺序获取锁
- 打破循环等待条件
12.2 避免死锁:运行时检测
银行家算法(了解即可)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| public class SafeLockDetector { private ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean(); public boolean detectDeadlock() { long[] deadlockedThreads = threadMXBean.findDeadlockedThreads(); if (deadlockedThreads != null && deadlockedThreads.length > 0) { System.out.println("检测到死锁!涉及线程数: " + deadlockedThreads.length); return true; } return false; } public void startMonitoring() { ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1); scheduler.scheduleAtFixedRate(() -> { if (detectDeadlock()) { System.out.println("发生死锁,需要处理!"); } }, 0, 5, TimeUnit.SECONDS); } }
|
代码解释:
ThreadMXBean:JVM 线程管理 Bean
findDeadlockedThreads():查找处于死锁状态的线程
- 返回线程 ID 数组,可以进一步分析
12.3 实际开发最佳实践
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66
| public class BestPracticeDemo { private final ReentrantLock lock1 = new ReentrantLock(); private final ReentrantLock lock2 = new ReentrantLock(); public void method1() { lock1.lock(); try { performTask(); } finally { lock1.unlock(); } } private final ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock(); public void read() { rwLock.readLock().lock(); try { System.out.println("Reading..."); } finally { rwLock.readLock().unlock(); } } public void write() { rwLock.writeLock().lock(); try { System.out.println("Writing..."); } finally { rwLock.writeLock().unlock(); } } synchronized (lockA) { synchronized (lockB) { } } public void safeMethod() { methodA(); methodB(); } private void methodA() { synchronized (lockA) { } } private void methodB() { synchronized (lockB) { } } }
|
总结
本文整理了 Java 多线程面试的核心知识点:
| 序号 |
知识点 |
重要程度 |
| 1 |
进程与线程的概念 |
⭐⭐⭐ |
| 2 |
线程与进程的关系、区别、优缺点 |
⭐⭐⭐⭐ |
| 3 |
Java 创建线程的多种方式 |
⭐⭐⭐⭐⭐ |
| 4 |
线程生命周期和6种状态 |
⭐⭐⭐⭐⭐ |
| 5 |
线程上下文切换 |
⭐⭐⭐⭐ |
| 6 |
wait() 为何定义在 Object 中 |
⭐⭐⭐⭐ |
| 7 |
run() vs start() |
⭐⭐⭐⭐ |
| 8 |
为什么要使用多线程 |
⭐⭐⭐⭐⭐ |
| 9 |
单核 CPU 与多线程 |
⭐⭐⭐⭐ |
| 10 |
多线程效率问题 |
⭐⭐⭐⭐ |
| 11 |
死锁概念 |
⭐⭐⭐⭐⭐ |
| 12 |
死锁预防与避免 |
⭐⭐⭐⭐⭐ |
参考资料:JavaGuide - Java并发编程面试题总结
原文链接:https://javaguide.cn/java/concurrent/java-concurrent-questions-01.html