并发多线程
阅读提示
建议按“线程基础 -> 并发三特性 -> 锁与 AQS -> 线程池 -> 线上排障”顺序复习。
面试回答时尽量带一个真实场景(如高并发下延迟抖动、线程池打满、锁竞争)。
本文以 Java SE 21 的规范和 API 为基准;基础 JMM、监视器和并发工具的语义也适用于 JDK 8/17。虚拟线程仅讨论 JDK 21,HotSpot 锁优化属于实现细节,需区分版本。
推荐训练法
- 先过题目清单,确认知识覆盖面。
- 每题先用 20~40 秒口述结论,再补机制细节。
- 对高频题(CAS、synchronized、线程池)做二次复述。
1、什么是进程、线程、协程?
回答提示:
- 进程提供资源与地址空间隔离;平台线程由操作系统调度,Java 虚拟线程由 JVM 调度到承载它的平台线程上。
- 协程通常是用户态轻量执行单元;具体调度和切换成本取决于实现,不能直接等同于 Java 线程。
参考回答:
- 一个进程可包含多个线程,线程共享进程资源但执行路径独立。
- 协程常用于高并发 IO 场景,通过用户态调度减少线程切换开销。
2、线程状态有哪些?
回答提示:
- Java 线程状态:
NEW、RUNNABLE、BLOCKED、WAITING、TIMED_WAITING、TERMINATED。
参考回答:
BLOCKED专指等待进入或重新进入对象监视器(synchronized),不是所有等锁场景;ReentrantLock竞争时通常通过 park 进入WAITING或TIMED_WAITING。RUNNABLE包含正在执行和等待处理器等操作系统资源的情形,不能仅凭它判断 CPU 正忙。状态定义见 Thread.State(Java 21)。
3、创建线程有哪些方式?
- 继承
Thread。 - 实现
Runnable。 - 实现
Callable,交给执行器提交并通过Future取结果;Callable/Future本身不会创建或启动线程。 - 通过
ExecutorService提交任务,由具体执行器决定线程管理方式。
JDK 21 可使用 Thread.ofVirtual().start(task) 或 Executors.newVirtualThreadPerTaskExecutor()。虚拟线程适合大量等待 IO 的任务,不会让 CPU 计算本身变快,也不应池化;下游并发限制可用 Semaphore。见 Oracle 虚拟线程指南。
4、并发三大特性是什么?
回答提示:
- 原子性:操作不可分割。
- 可见性:通过 happens-before 等同步关系,保证一个线程的写入对另一个线程的相应读取可见,不是任意共享变量都能“立即看到更新”。
- 有序性:程序执行顺序受约束。
参考回答:
synchronized可保证原子性和可见性;volatile保证可见性并约束重排序。
机制上,同一监视器的解锁 happens-before 后续加锁;对同一 volatile 变量的写 happens-before 后续读,并通过传递性发布此前写入的普通字段。这不意味着所有指令都禁止重排,而是重排不能破坏规范保证的可观察行为。count++ 包含读、加一、写回,即使 count 为 volatile,多个线程仍可能覆盖结果;计数可用 AtomicInteger.incrementAndGet()。见 JLS 21 §17.4.5。
5、什么是 CAS?优缺点是什么?
回答提示:
- CAS:比较并交换,原子指令,常用于无锁并发。
- 优点:可用于实现无锁更新,避免为单次更新挂起线程;吞吐优势取决于竞争程度。
- 缺点:可能遇到 ABA,重试循环会消耗 CPU;单次 CAS 更新一个位置,多字段不变量需锁或将不可变状态封装后用
AtomicReference整体替换。
参考回答:
compareAndSet失败返回false,不会自行无限重试;调用方或上层原子更新方法决定是否循环。竞争激烈时应评估重试成本。见 atomic 包说明。
6、什么是 ABA 问题?如何解决?
回答提示:
- 值从 A -> B -> A,CAS 看起来没变但过程已变。
- 可用版本号机制解决,如
AtomicStampedReference。
7、synchronized 的底层和使用范围
回答提示:
- 修饰实例方法:锁对象实例。
- 修饰静态方法:锁 Class 对象。
- 修饰代码块:锁指定对象。
参考回答:
synchronized是可重入、独占的监视器同步;正常返回或异常退出同步区域都会释放监视器。HotSpot 的锁表示与优化不是语言保证,不能把“偏向锁 → 轻量级锁 → 重量级锁”当成所有 JDK 的固定流程。JDK 15 已默认禁用并弃用偏向锁,见 Oracle JDK 15 发布说明。
8、volatile 能保证什么?不能保证什么?
回答提示:
- 能保证可见性和一定有序性。
- 不能保证复合操作原子性(如
count++)。
9、ReentrantLock 和 synchronized 有什么区别?
ReentrantLock支持可中断加锁、超时尝试、公平锁。synchronized语法简单,JVM 层优化充分。- 复杂并发控制(多条件队列)更适合
ReentrantLock。
10、什么是 AQS?
回答提示:
- AQS(AbstractQueuedSynchronizer)是并发同步框架核心。
- 通过原子更新的
int state与 FIFO 等待队列组织竞争;子类定义获取/释放状态的规则,AQS 负责排队、阻塞与唤醒。常说的 CLH 队列是其队列设计来源,不能据此断言锁一定公平。
参考回答:
ReentrantLock、Semaphore、CountDownLatch的内部同步器都基于 AQS。入队不等于严格按先后获得锁,是否允许新线程插队取决于获取逻辑。见 AQS API。
11、wait / notify / notifyAll 和 sleep 的区别
wait必须在持有该对象监视器时调用(同步方法或同步块均可),只释放该对象的监视器,不释放持有的其他锁。sleep让当前线程暂停一段时间,不释放已持有的监视器,也不提供同步可见性保证。notify任意选择一个等待线程,不承诺随机或公平;notifyAll通知全部等待线程。两者同样要求持有该对象监视器,且不会立刻释放它。被通知线程重新获得监视器后才能从wait返回。
等待必须放在条件循环中:虚假唤醒或其他线程抢先消耗条件,都要求重新判断。以下为机制片段,ready 的读写均须由同一个 monitor 保护,调用方法需声明或处理 InterruptedException:
// 等待方
synchronized (monitor) {
while (!ready) {
monitor.wait();
}
// 在锁内处理依赖 ready 的共享状态
}
// 通知方
synchronized (monitor) {
ready = true;
monitor.notifyAll();
}见 Object.wait / notify(Java 21)。
12、CountDownLatch、CyclicBarrier、Phaser 的区别
CountDownLatch:一次性门闩,常用于主线程等待子任务结束。CyclicBarrier:可复用屏障,常用于多线程分阶段对齐。Phaser:更灵活,支持动态注册与多阶段同步。
13、Semaphore 适合什么场景?
回答提示:
- 控制同时访问资源的数量,典型场景是连接池资源保护。它限制的是并发数,不直接保证每秒请求数;成功获取许可后应在
finally中释放。
14、ThreadLocal 的作用和风险
回答提示:
- 作用:线程隔离上下文(如用户信息、追踪ID)。
- 风险:在线程池中不清理会导致上下文串到后续任务,也可能长期保留对象。请求上下文宜在
try/finally中remove();线程隔离只隔离变量绑定,若多个线程绑定同一个可变对象,对象本身仍需同步。
15、为什么要用线程池?
- 降低线程创建/销毁开销。
- 控制并发度,保护系统资源。
- 便于统一监控和治理。
16、线程池核心参数有哪些?
corePoolSizemaximumPoolSizekeepAliveTime及其时间单位unitworkQueuethreadFactoryRejectedExecutionHandler
在运行状态下,execute 的核心顺序是:
少于 corePoolSize → 尝试新建工作线程
否则 → 尝试入队
入队失败 → 尝试扩至 maximumPoolSize
仍无法接收(或池已关闭)→ 拒绝处理入队后还需复查池状态,避免关闭竞态漏处理任务。无界队列通常让任务积压而非触发扩容,因此仅调大 maximumPoolSize 往往无效;有界队列才能同时约束积压量。默认仅超出核心数的空闲线程超时退出,开启 allowCoreThreadTimeOut(true) 且超时为正数后核心线程也可退出。详见 ThreadPoolExecutor 官方说明。
17、线程池拒绝策略有哪些?
AbortPolicy:直接抛异常。CallerRunsPolicy:池未关闭时由提交线程执行,被关闭后丢弃任务。DiscardOldestPolicy:池未关闭时移除队列头任务,再尝试提交新任务;并非丢弃正在执行或全局最旧的任务。DiscardPolicy:静默丢弃被拒绝的任务。
静默丢弃通过 submit 提交的任务可能让对应 Future 一直无法完成,不能再无期限等待它。
18、线程池参数怎么估算?
回答提示:
- CPU 密集:线程数接近 CPU 核数。
- IO 密集的平台线程池可按等待/计算时间比例估算初值;“2N”没有通用保证,还受下游容量、连接数与内存约束。
参考回答:
- 实际工程要结合压测、队列积压、RT 与 CPU 使用率动态调参。
19、JMM 是什么?为什么需要它?
回答提示:
- JMM 是 Java 内存访问规则,统一不同硬件平台的并发语义。
- 解决可见性、有序性和跨平台一致性问题。
20、乐观锁和悲观锁怎么理解?
- 悲观锁:先加锁再操作,冲突场景更稳。
- 乐观并发控制:读取状态并计算候选结果,再通过 CAS 或版本条件原子地校验并提交;失败后重试或放弃。不能先修改共享数据再事后校验,也不保证读多写少时一定更快。
21、如何定位线上并发问题?
- 先看监控:线程数、CPU、队列长度、拒绝次数。
- 再看 JVM:
jstack、jcmd、GC 日志。 - 最后看代码:锁粒度、线程池配置、共享变量访问。
22、面试表达模板(可直接复述)
- 我会先判断是“计算瓶颈”还是“并发控制瓶颈”,再决定优先调线程池还是降锁竞争。
- 对线程池我会重点盯队列积压和拒绝策略,避免任务堆死。
- 对锁问题我会通过线程 dump 定位阻塞链,再做锁粒度优化或改无锁结构。
继续阅读与资料
继续追问:线程池为何不扩容,队列与拒绝如何影响调用方。
