《Java 100 天进阶之路》第60篇:并发编程面试压轴题(2026版)
第60篇:并发编程面试压轴题(2026版)
📌系列导航:《Java 100 天进阶之路》完整目录 |
⬅️ 上一篇:第59篇:ThreadLocal原理与内存泄漏 |
➡️ 下一篇:第61篇:JVM内存区域(待发布)
🗺️ 本文阅读地图(3 分钟速览)
第51~59篇从线程基础一路打到 ThreadLocal,本篇是多线程与高并发专题的收官之作。精选 30+ 道大厂高频面试题,覆盖 7 大模块,每题配标准话术 + 加分回答:
| 模块 | 核心考点 | 面试权重 |
|---|---|---|
| 基础概念 | 线程状态、创建方式、sleep vs wait、start vs run | ⭐⭐⭐⭐ |
| 锁机制 | synchronized 锁升级、对象头 Mark Word、ReentrantLock 对比 | ⭐⭐⭐⭐⭐ |
| JMM与volatile | JMM 抽象结构、可见性、有序性、内存屏障、happens-before | ⭐⭐⭐⭐⭐ |
| AQS与并发工具 | AQS 核心三要素、独占/共享模式、Semaphore/CountDownLatch | ⭐⭐⭐⭐⭐ |
| 线程池 | 七大参数、执行流程、拒绝策略、Executors 陷阱 | ⭐⭐⭐⭐⭐ |
| 死锁与排查 | 四大必要条件、jstack 实战、死锁 vs 活锁 vs 饥饿 | ⭐⭐⭐⭐ |
| 异步与无锁 | CompletableFuture 编排、CAS 三要素、ABA 问题、LongAdder | ⭐⭐⭐⭐ |
| ThreadLocal | 内存泄漏根因、弱引用/强引用、remove 必要性 | ⭐⭐⭐⭐ |
文章目录
- 第60篇:并发编程面试压轴题(2026版)
- 🗺️ 本文阅读地图(3 分钟速览)
- 一、基础概念篇(4题)
- 二、锁机制篇(6题)
- 三、JMM与volatile篇(4题)
- 四、AQS与并发工具篇(5题)
- 五、线程池篇(5题)
- 六、死锁与排查篇(3题)
- 七、异步与无锁篇(4题)
- 八、ThreadLocal篇(3题)
- 九、面试官追问陷阱(加分题)
- 十、练习题
- 📊 你的学习进度
- 👉 下一篇文章预告
一、基础概念篇(4题)
Q1:Java 线程有哪几种状态?状态之间如何转换?
答案:6 种状态:NEW(新建,线程对象创建未启动)、RUNNABLE(可运行,包含就绪+运行中)、BLOCKED(阻塞,等待获取监视器锁)、WAITING(无限等待,需notify()/unpark()唤醒)、TIMED_WAITING(有限等待,超时自动唤醒)、TERMINATED(终止,run()结束或异常退出)。
转换路径:NEW →start()→ RUNNABLE → 进入synchronized锁被占用 → BLOCKED → 获取锁 → RUNNABLE。RUNNABLE →wait()/join()/park()→ WAITING →notify()/unpark()/超时 → RUNNABLE。RUNNABLE →sleep(ms)/wait(ms)→ TIMED_WAITING → 超时/唤醒 → RUNNABLE。RUNNABLE →run()结束/异常 → TERMINATED。
💡加分:操作系统线程通常有 5 种状态,JVM 将“就绪”和“运行中”合并为 RUNNABLE。
Q2:sleep()和wait()的区别?
答案:sleep()是 Thread 的静态方法,不释放锁,时间到自动醒来,可在任何地方使用。wait()是 Object 的实例方法,释放锁,需要notify()/notifyAll()唤醒,必须在synchronized块中调用。
Q3:start()和run()的区别?
答案:start()启动新线程,由 JVM 调用run()执行;直接调用run()只会在当前线程执行,不会创建新线程。
Q4:线程有哪几种创建方式?各有什么优缺点?
答案:4 种方式:① 继承 Thread(简单但单继承限制,不推荐);② 实现 Runnable(可继承其他类,推荐);③ 实现 Callable(有返回值、可抛异常);④ 线程池(资源复用,推荐)。生产环境优先线程池 + Runnable/Callable。
二、锁机制篇(6题)
Q5:synchronized 的锁升级过程是什么?
答案:synchronized 的锁状态存储在对象头的 Mark Word 中。升级路径为:无锁 → 偏向锁 → 轻量级锁 → 重量级锁,单向升级不可逆。
- 偏向锁:Mark Word 记录偏向线程 ID,同一线程再次进入无需 CAS,几乎零开销。一旦有第二个线程竞争,撤销偏向。
- 轻量级锁:线程在自己的栈帧中创建 Lock Record,通过 CAS 将 Mark Word 替换为指向 Lock Record 的指针。竞争失败则自旋等待。
- 重量级锁:自旋超时或竞争激烈时,锁膨胀为重量级锁,Mark Word 指向 ObjectMonitor,竞争线程进入 OS 阻塞队列。
💡加分:JDK 15+ 默认禁用偏向锁(
-XX:-UseBiasedLocking),但面试仍需理解原理。
Q6:synchronized 的三种使用方式及锁对象?
答案:① 修饰实例方法 → 锁当前实例this;② 修饰静态方法 → 锁当前类的 Class 对象;③ 修饰代码块 → 锁括号内指定的对象。
Q7:synchronized 和 ReentrantLock 的区别?
答案:synchronized 是 JVM 内置关键字,自动释放锁,支持锁升级(偏向→轻量→重量),非公平;不可中断、不支持超时。ReentrantLock 是 JDK API,需finally手动unlock(),无锁升级,支持公平/非公平、lockInterruptibly()、tryLock(timeout)、多个 Condition。
💡加分:虚拟线程中慎用 synchronized(会导致 Pinning),建议用 ReentrantLock。
Q8:synchronized 锁存在哪里?
答案:存在 Java 对象头的 Mark Word 中。64 位 JVM 中 Mark Word 占 8 字节,存储锁状态、线程 ID、hashCode、分代年龄等信息。
Q9:什么是偏向锁的撤销?
答案:当其他线程尝试获取已被偏向的锁时,触发偏向撤销。JVM 需到达安全点暂停持有锁的线程,检查其是否存活及是否仍在同步块中。如已退出则撤销偏向恢复无锁;如仍在执行则升级为轻量级锁。
Q10:JVM 对 synchronized 做了哪些优化?
答案:① 锁升级(偏向→轻量→重量);② 自适应自旋锁(JVM 动态调整自旋次数);③ 锁消除(逃逸分析移除无竞争锁);④ 锁粗化(合并连续加锁/解锁)。
三、JMM与volatile篇(4题)
Q11:JMM(Java 内存模型)是什么?和 JVM 内存结构有什么区别?
答案:JMM 是 Java 语言规范定义的内存访问规则,解决多线程间“数据何时可见、执行顺序如何保证”的问题。JVM 内存结构解决“数据存在哪里”——堆、栈、方法区等。两者完全不同——一个管“怎么看见”,一个管“存哪里”。
💡加分:JMM 的抽象结构包括主内存(共享变量存储)和每个线程私有的工作内存(CPU 缓存抽象)。
Q12:volatile 能保证原子性吗?为什么?
答案:不能。volatile 只保证单次读写操作的可见性,但count++包含“读-改-写”三个字节码步骤,volatile 无法保证这三步不被其他线程打断。
Q13:volatile 如何保证可见性?
答案:volatile 写操作强制将本地内存修改刷新到主内存;volatile 读操作强制使本地内存副本失效,从主内存重新读取。通过内存屏障(StoreStore、StoreLoad、LoadLoad、LoadStore)禁止指令重排序,配合 MESI 缓存一致性协议实现跨线程可见性。
Q14:happens-before 规则有哪些?
答案:核心规则包括:程序顺序规则(同一线程内,前面的操作 happens-before 后面的)、volatile 变量规则(volatile 写 happens-before 后续对同一 volatile 的读)、锁规则(解锁 happens-before 后续加锁)、传递性(A happens-before B,B happens-before C → A happens-before C)。
四、AQS与并发工具篇(5题)
Q15:AQS 是什么?核心思想是什么?
答案:AQS(AbstractQueuedSynchronizer)是 JUC 并发包的底层基石。核心三要素:①volatile int state(同步状态);② CLH 双向 FIFO 队列(等待线程队列);③ 模板方法模式(子类只需实现tryAcquire/tryRelease等方法)。
💡加分:AQS 提供独占模式(ReentrantLock)和共享模式(Semaphore、CountDownLatch)。
Q16:ReentrantLock 的可重入性是如何实现的?
答案:在tryAcquire中,先检查 state:0 则 CAS 抢锁;大于 0 则判断当前线程是否已是锁持有者,是则state++(重入),否则获取失败。解锁时每次state--,归零才释放锁。
Q17:CountDownLatch 和 Semaphore 的区别?
答案:CountDownLatch 是倒计数门栓,countDown()减 1,归零唤醒所有等待线程,计数器不能重置。Semaphore 是信号量,acquire()减 1,release()加 1,许可数可动态增减,用于限流控制并发访问数。
Q18:AQS 的共享模式和独占模式有什么区别?
答案:独占模式释放锁时只唤醒一个后继线程。共享模式释放锁时会传播唤醒——如果当前节点状态为PROPAGATE,会继续唤醒后继节点,让多个等待线程同时获取共享锁。
Q19:Condition 的await()和signal()与wait()/notify()有什么区别?
答案:wait()/notify()是 Object 方法,与 synchronized 配合,只能有一个等待队列。Condition是 ReentrantLock 配套,支持多个 Condition(多个等待队列),支持中断响应和超时等待。
五、线程池篇(5题)
Q20:ThreadPoolExecutor 的七大参数是什么?
答案:corePoolSize(核心线程数)、maximumPoolSize(最大线程数)、keepAliveTime(空闲存活时间)、unit(时间单位)、workQueue(工作队列)、threadFactory(线程工厂)、handler(拒绝策略)。
Q21:任务提交后线程池的执行流程?
答案:三步判断:① 当前线程数 < corePoolSize → 创建核心线程执行;② 队列未满 → 放入队列等待;③ 线程数 < maximumPoolSize → 创建非核心线程执行;④ 否则执行拒绝策略。
💡关键理解:队列满了才创建非核心线程。无界队列时,maximumPoolSize 和拒绝策略都失效。
Q22:线程池的四种拒绝策略?
答案:① AbortPolicy(抛异常,默认);② CallerRunsPolicy(调用者线程执行,可防止任务丢失);③ DiscardPolicy(静默丢弃);④ DiscardOldestPolicy(丢弃最旧任务)。
Q23:为什么禁止使用 Executors 创建线程池?
答案:newFixedThreadPool使用无界 LinkedBlockingQueue,任务堆积导致 OOM。newCachedThreadPool最大线程数为Integer.MAX_VALUE,高并发下创建海量线程导致资源耗尽。
💡加分:生产环境必须手动创建 ThreadPoolExecutor,使用有界队列 + 明确拒绝策略。
Q24:corePoolSize 如何设置?
答案:CPU 密集型:CPU 核数 + 1。IO 密集型:CPU 核数 × 2 或更高。
六、死锁与排查篇(3题)
Q25:死锁的四个必要条件?
答案:互斥(资源一次只能被一个线程占用)、持有并等待(持有资源的同时等待其他资源)、不可剥夺(资源只能主动释放,不能强行剥夺)、循环等待(线程之间形成等待环路)。
Q26:如何排查 Java 死锁?
答案:①jps -l找到 Java 进程 PID;②jstack -l <PID>生成线程快照,搜索Found one Java-level deadlock;③ 分析waiting to lock和locked信息,定位代码行号。
💡加分:也可用 jconsole 一键检测死锁,或 Arthas 在线诊断。
Q27:死锁、活锁、饥饿有什么区别?
答案:死锁是线程互相等待资源,永久阻塞。活锁是线程不阻塞但不断重试失败,无法推进任务(“空转”)。饥饿是低优先级线程长期抢不到资源,有进展但极其缓慢。
七、异步与无锁篇(4题)
Q28:CompletableFuture 和 Future 的区别?
答案:Future 只能通过get()阻塞获取结果,无法链式调用、无法组合多个任务、异常处理困难。CompletableFuture 支持链式调用(thenApply)、任务组合(thenCombine、allOf)、非阻塞回调(whenComplete)和优雅异常处理(exceptionally)。
Q29:thenApply和thenCompose的区别?
答案:thenApply是同步转换,返回转换后的值,不创建新的 CompletableFuture。thenCompose连接两个有依赖关系的异步任务,返回新的 CompletableFuture,避免CompletableFuture<CompletableFuture<U>>嵌套。
Q30:CAS 的三要素是什么?有哪些优缺点?
答案:三要素:内存位置 V、预期值 A、新值 B。优点:无锁、无上下文切换、低竞争下性能极高。缺点:① ABA 问题;② 高冲突下自旋消耗 CPU;③ 只能保证单个变量的原子性。
💡加分:CAS 是 CPU 原子指令(x86 下为
lock cmpxchg),原子性由硬件保证。
Q31:什么是 ABA 问题?如何解决?
答案:变量从 A 变成 B 又变回 A,CAS 误判为未被修改。解决方案:用AtomicStampedReference(版本号)或AtomicMarkableReference(标记位)。
💡加分:ABA 问题的危害在于中间状态丢失,在无锁栈、无锁队列中可能导致链表断裂。
Q32:LongAdder 为什么比 AtomicLong 快?
答案:LongAdder 采用分段累加设计:低并发时直接 CAS 更新 base;高并发时将累加操作分散到多个 Cell 上,每个线程各自累加自己的 Cell,最后求和。避免所有线程竞争同一个变量。
八、ThreadLocal篇(3题)
Q33:ThreadLocal 的内存泄漏是怎么产生的?
答案:ThreadLocalMap 的 Entry 用弱引用存储 key(ThreadLocal 实例),用强引用存储 value。当 ThreadLocal 对象外部引用被置空后,GC 回收 ThreadLocal 实例,Entry.key 变为 null,但 value 仍被强引用。线程存活时,null-key Entry 永久残留 → 内存泄漏。
💡加分:弱引用只是“兜底”,根治方法只有用完手动
remove()。
Q34:remove()和set(null)有什么区别?
答案:remove()从 ThreadLocalMap 中删除整个 Entry(key 和 value 都被清除),彻底释放内存。set(null)只是把 value 设为 null,但 Entry 本身仍存在,key 仍是当前 ThreadLocal 实例,Entry 不会被回收。
Q35:线程池中如何使用 ThreadLocal?
答案:线程池核心线程长期存活,ThreadLocalMap 一直存在。必须每次任务结束在try-finally中调用remove(),否则会导致内存泄漏和脏读(后续请求读到前一个请求的数据)。
💡加分:跨线程池传递上下文用阿里开源的
TransmittableThreadLocal(TTL)。
九、面试官追问陷阱(加分题)
追问1:“synchronized的锁升级是可逆的吗?”
👉不可逆。锁只能从偏向锁 → 轻量级锁 → 重量级锁单向升级,不能降级。这是 JDK 的设计决策,因为降级需要重新评估竞争状态,会增加复杂性和开销。
追问2:“无界队列时maximumPoolSize还有意义吗?”
👉没有意义。无界队列永远不会满,线程池永远不会创建非核心线程,
maximumPoolSize和拒绝策略都失效。
追问3:“join()和get()有什么区别?生产中用哪个?”
👉
get()抛出受检异常ExecutionException,需 try-catch。join()抛出非受检异常CompletionException,代码更简洁。生产推荐用join(),但需注意拆cause才能拿到原始异常。
追问4:“volatile能保证 64 位(long/double)的原子性吗?”
👉 可以。JVM 规范保证 volatile long/double 的读写是原子的。普通 long/double 在 32 位 JVM 上可能被拆分为两次 32 位操作,但 volatile 强制原子读写。
追问5:“ConcurrentHashMap的size()准确吗?”
👉 不准确,返回的是近似值。
baseCount和CounterCell累加过程中仍有并发修改,为了性能主动放弃强一致性。
十、练习题
源码题:用
jstack -l <pid>分析一个死锁程序的线程 dump,找到死锁线程和代码行号。配置题:手动创建一个线程池,要求:核心线程数 8、最大线程数 32、空闲存活 60 秒、有界队列容量 200、拒绝策略为 CallerRunsPolicy、自定义线程名“io-pool-%d”。
场景题:某系统使用
Executors.newFixedThreadPool(10),高并发下出现 OOM。分析原因并提出解决方案。手撕题:用 CompletableFuture 实现一个“并行查询三个下游接口,3 秒超时返回兜底值”的方法。
📊 你的学习进度
- 当前:第60篇 / 共108篇 ·进阶篇:并发编程与JUC详解(第51~60篇)
- ✅ 已完成:基础篇44篇 + 第45~60篇
- 📖 正在学:第60篇
- ⏳ 待学习:第61~108篇
👉 📚 完整目录 & 学习指南 | 🔥 订阅本专栏,不错过每一篇
👉 下一篇文章预告
🚀下一篇:《第61篇:JVM内存区域》
内容简介:JVM 内存布局(堆、元空间、虚拟机栈、本地方法栈、程序计数器)、JDK 8 变化、各区域内存溢出场景与排查。
👉多线程与高并发专题正式收官!JVM 调优专题即将开启!
📌《Java 100 天进阶之路 | 从入门到上岗就业》每天一篇,建议收藏 + 关注,一起100天拿offer!
👉 点击关注我,更新后第一时间收到推送!
