一、并发编程基础概念
1. 进程与线程
核心定义
- 进程:程序运行的实例,是操作系统资源分配(以内存为主)的最小单位。进程拥有独立的内存空间、IO 资源等,进程间相互独立,通信复杂(IPC 机制或网络协议)。
- 线程:CPU 调度的最小单位,依赖进程存在,是进程内的执行实体。线程仅拥有程序计数器、寄存器、栈等必要资源,共享所属进程的全部资源,线程间通信简单(共享内存)。
核心区别
| 对比维度 | 进程 | 线程 |
|---|---|---|
| 资源分配 | 最小单位 | 无独立资源(共享进程资源) |
| 调度单位 | 操作系统调度进程 | CPU 调度线程 |
| 通信复杂度 | 复杂(IPC / 网络协议) | 简单(共享内存) |
| 上下文切换成本 | 高 | 低 |
2. 并发与并行
- 并发(Concurrent):同一时间单位内交替执行多个任务(微观串行,宏观并行)。例如单 CPU 核心下多线程切换执行。
- 并行(Parallel):同一时刻同时执行多个任务。例如多核 CPU 下,每个核心独立调度线程执行。

3. 上下文切换
- 定义:CPU 从一个线程 / 进程切换到另一个线程 / 进程时,保存当前线程 / 进程的 CPU 状态(寄存器、程序计数器等),加载目标线程 / 进程状态的过程。
- 成本:一次上下文切换需 5000~20000 个时钟周期,远高于普通指令执行成本,是并发编程性能损耗的核心原因之一。
- 触发场景:线程 / 进程切换、系统调用、IO 阻塞等。
二、Java 线程核心知识点
1. Java 线程的创建方式
-
方式 1:继承 Thread 类,重写 run () 方法(线程与任务耦合)。
Thread t1 = new Thread("t1") {
@Override
public void run() {
System.out.println("Thread run");
}
};
t1.start(); -
方式 2:实现 Runnable 接口,配合 Thread 类(线程与任务解耦,推荐)。
Runnable task = () -> System.out.println("Runnable run");
Thread t2 = new Thread(task, "t2");
t2.start();
扩展方式(本质基于 Runnable)
-
方式 3:实现 Callable 接口,配合 FutureTask(支持任务返回值)。
FutureTask<Integer> task = new FutureTask<>(() -> {
System.out.println("Callable run");
return 100;
});
new Thread(task, "t3").start();
Integer result = task.get(); // 阻塞获取结果
面试易错点
- 误区:认为 Callable 是独立的创建方式。本质:Callable 需通过 FutureTask 包装为 Runnable,再交给 Thread 执行,属于 Runnable 方式的扩展。
2. Java 线程生命周期(6 种状态)
状态定义(Thread.State 枚举)
- NEW:线程对象创建但未调用 start () 方法。
- RUNNABLE:包含就绪(ready)和运行中(running)状态,线程等待 CPU 调度或正在执行。
- BLOCKED:线程阻塞于锁(synchronized 竞争失败)。
- WAITING:线程等待其他线程通知(Object.wait ()、Thread.join () 等),无超时时间。
- TIMED_WAITING:带超时时间的等待(Thread.sleep (long)、Object.wait (long) 等)。
- TERMINATED:线程执行完毕或异常终止。

3. 线程核心方法
关键方法对比
| 方法名 | 静态属性 | 功能说明 | 注意事项 |
|---|---|---|---|
| start() | 否 | 启动线程,进入就绪状态,触发 run () 执行 | 不可重复调用,否则抛 IllegalThreadStateException |
| run() | 否 | 线程执行的任务逻辑 | 直接调用仅为普通方法,不启动新线程 |
| join() | 否 | 等待线程执行完毕 | 用于线程同步 |
| sleep(long n) | 是 | 当前线程休眠 n 毫秒,不释放锁 | 可被 interrupt () 中断,抛出 InterruptedException |
| yield() | 是 | 让出 CPU 使用权,进入就绪状态 | 仅为提示,调度器可忽略 |
| interrupt() | 否 | 中断线程 | 阻塞线程会抛异常并清空中断标记,运行中线程仅设标记 |
| isInterrupted() | 否 | 判断线程是否被中断 | 不清除中断标记 |
| interrupted() | 是 | 判断当前线程是否被中断 | 清除中断标记 |
常用方法区别
- sleep vs yield :
- sleep:主动休眠,释放 CPU,不释放锁,休眠时间可指定。
- yield:主动让出 CPU,不释放锁,无休眠时间,仅让优先级相同或更高的线程有机会执行。
- wait vs notify/notifyAll :
- 必须在 synchronized 代码块中调用,调用时释放对象锁。
- wait:线程进入等待队列,需被 notify/notifyAll 唤醒。
- notify:唤醒一个等待线程,notifyAll:唤醒所有等待线程。
4. 守护线程
- 定义:服务于非守护线程的线程,当所有非守护线程结束时,守护线程无论是否执行完毕都会被强制终止。
- 应用场景:垃圾回收器、心跳检测、事件监听等后台任务。
- 创建方式 :
thread.setDaemon(true)(需在 start () 前调用)。
三、并发编程核心机制
1. 线程间通信方式
核心方式
| 通信方式 | 适用场景 | 特点 |
|---|---|---|
| 共享内存(volatile / 共享变量) | 简单数据传递 | 需保证可见性、原子性 |
| 管道流(PipedInputStream/PipedOutputStream) | 线程间字节 / 字符传输 | 基于内存,无需磁盘 IO |
| 等待 / 通知机制(wait/notify、LockSupport) | 线程协作(生产者 - 消费者) | 高效,减少轮询开销 |
| Thread.join() | 线程顺序执行 | 同步等待线程完成 |
等待 / 通知机制规范
-
等待方:获取锁 → 条件不满足则 wait () → 被唤醒后再次检查条件 → 执行逻辑。
synchronized (lock) {
while (!condition) {
lock.wait(); // 避免虚假唤醒,必须用while循环
}
// 业务逻辑
} -
通知方:获取锁 → 修改条件 → 通知所有等待线程。
synchronized (lock) {
condition = true;
lock.notifyAll(); // 优先用notifyAll,避免唤醒错误线程
}
LockSupport(park/unpark)
-
无需锁支持,可在任意位置阻塞 / 唤醒线程。
-
支持先 unpark 后 park(不会阻塞),适合复杂线程协作场景。
Thread t = new Thread(() -> {
LockSupport.park(); // 阻塞
System.out.println("被唤醒");
});
t.start();
LockSupport.unpark(t); // 唤醒
2. 线程同步机制
核心同步方式
- volatile :
- 保证可见性(一个线程修改后,其他线程立即可见)和有序性(禁止指令重排序)。
- 不保证原子性,适用于 "单写多读" 场景。
- synchronized :
- 保证原子性、可见性、有序性,是重量级锁(JDK1.6 后优化为偏向锁、轻量级锁、重量级锁)。
- 可修饰方法、代码块,锁对象为类对象或实例对象。
- Lock 接口(ReentrantLock 等) :
- 可重入、可中断、可超时、支持公平锁 / 非公平锁,灵活性高于 synchronized。
- 需手动释放锁(try-finally 中调用 unlock ())。
原子操作
- CAS(Compare and Swap):无锁同步机制,通过硬件指令保证原子性。
- Atomic 类:基于 CAS 实现,如 AtomicInteger、AtomicReference 等,支持原子性的增删改操作。
四、并发设计模式
1. 优雅终止线程:两阶段终止模式
核心思想
- 第一阶段:发送终止请求(通过 interrupt () 唤醒阻塞线程)。
- 第二阶段:线程检查终止标志位,执行清理工作后退出。
实现代码
public class MonitorThread extends Thread {
private volatile boolean terminated = false; // 终止标志位
@Override
public void run() {
while (!terminated && !Thread.interrupted()) {
try {
System.out.println("监控中...");
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt(); // 重置中断标志
break;
}
}
System.out.println("执行清理工作...");
}
public void terminate() {
terminated = true;
this.interrupt(); // 唤醒阻塞线程
}
}
线程池终止
- shutdown():拒绝新任务,等待队列中任务执行完毕后关闭。
- shutdownNow():拒绝新任务,中断正在执行的任务,返回未执行的任务列表。
2. 避免共享:三大设计模式
| 模式 | 核心思想 | 应用场景 | 注意事项 |
|---|---|---|---|
| 不变性模式 | 对象创建后状态不可修改(final 修饰属性 + 只读方法) | 缓存、配置信息、值对象 | 需保证属性对象也不可变 |
| 写时复制模式(Copy-on-Write) | 写操作时复制副本,修改后替换原数据 | 读多写少场景(如路由表) | 写操作消耗内存,适用于写少场景 |
| 线程本地存储模式(ThreadLocal) | 为每个线程分配独立存储,数据隔离 | 保存线程上下文(如用户会话) | 线程池环境需手动 remove (),避免内存泄漏 |
ThreadLocal 内存泄漏问题
- 原因:ThreadLocalMap 中 Entry 的 key 为弱引用,线程结束后若未手动清理,value 可能被长期引用。
- 解决方案:使用后调用
threadLocal.remove()清理。
3. 多线程分工模式
| 模式 | 核心思想 | 应用场景 | 实现方式 |
|---|---|---|---|
| Thread-Per-Message | 为每个任务创建独立线程 | 低并发异步场景(如定时任务) | new Thread ()(高并发需线程池) |
| Worker Thread | 线程池复用线程,避免频繁创建销毁 | 高并发场景(如服务端处理请求) | Executors.newFixedThreadPool () 等 |
| 生产者 - 消费者模式 | 生产者生产任务入队,消费者从队列取任务执行 | 任务解耦、削峰填谷 | BlockingQueue + 线程池 |
生产者 - 消费者模式实现
public class ProducerConsumer {
private static final BlockingQueue<String> queue = new ArrayBlockingQueue<>(5);
public static void main(String[] args) {
// 生产者
new Thread(() -> {
try {
while (true) {
queue.put("任务");
System.out.println("生产任务,队列大小:" + queue.size());
Thread.sleep(1000);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
// 消费者
new Thread(() -> {
try {
while (true) {
String task = queue.take();
System.out.println("消费任务,队列大小:" + queue.size());
Thread.sleep(2000);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
}
}
4. 多线程版本 if 模式
| 模式 | 核心思想 | 区别 | 应用场景 |
|---|---|---|---|
| 守护挂起模式(Guarded Suspension) | 条件不满足时线程等待,直到条件满足 | 等待条件满足后执行 | join ()、Future 实现 |
| 避免执行模式(Balking) | 条件不满足时直接返回,不等待 | 无需等待,直接放弃 | 自动保存、单次初始化 |
