《Java 100 天进阶之路》第60篇:并发编程面试压轴题(2026版)

《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与volatileJMM 抽象结构、可见性、有序性、内存屏障、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题Q1Java 线程有哪几种状态状态之间如何转换答案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。Q2sleep()和wait()的区别答案sleep()是 Thread 的静态方法不释放锁时间到自动醒来可在任何地方使用。wait()是 Object 的实例方法释放锁需要notify()/notifyAll()唤醒必须在synchronized块中调用。Q3start()和run()的区别答案start()启动新线程由 JVM 调用run()执行直接调用run()只会在当前线程执行不会创建新线程。Q4线程有哪几种创建方式各有什么优缺点答案4 种方式① 继承 Thread简单但单继承限制不推荐② 实现 Runnable可继承其他类推荐③ 实现 Callable有返回值、可抛异常④ 线程池资源复用推荐。生产环境优先线程池 Runnable/Callable。二、锁机制篇6题Q5synchronized 的锁升级过程是什么答案synchronized 的锁状态存储在对象头的 Mark Word 中。升级路径为无锁 → 偏向锁 → 轻量级锁 → 重量级锁单向升级不可逆。偏向锁Mark Word 记录偏向线程 ID同一线程再次进入无需 CAS几乎零开销。一旦有第二个线程竞争撤销偏向。轻量级锁线程在自己的栈帧中创建 Lock Record通过 CAS 将 Mark Word 替换为指向 Lock Record 的指针。竞争失败则自旋等待。重量级锁自旋超时或竞争激烈时锁膨胀为重量级锁Mark Word 指向 ObjectMonitor竞争线程进入 OS 阻塞队列。加分JDK 15 默认禁用偏向锁-XX:-UseBiasedLocking但面试仍需理解原理。Q6synchronized 的三种使用方式及锁对象答案① 修饰实例方法 → 锁当前实例this② 修饰静态方法 → 锁当前类的 Class 对象③ 修饰代码块 → 锁括号内指定的对象。Q7synchronized 和 ReentrantLock 的区别答案synchronized 是 JVM 内置关键字自动释放锁支持锁升级偏向→轻量→重量非公平不可中断、不支持超时。ReentrantLock 是 JDK API需finally手动unlock()无锁升级支持公平/非公平、lockInterruptibly()、tryLock(timeout)、多个 Condition。加分虚拟线程中慎用 synchronized会导致 Pinning建议用 ReentrantLock。Q8synchronized 锁存在哪里答案存在 Java 对象头的 Mark Word 中。64 位 JVM 中 Mark Word 占 8 字节存储锁状态、线程 ID、hashCode、分代年龄等信息。Q9什么是偏向锁的撤销答案当其他线程尝试获取已被偏向的锁时触发偏向撤销。JVM 需到达安全点暂停持有锁的线程检查其是否存活及是否仍在同步块中。如已退出则撤销偏向恢复无锁如仍在执行则升级为轻量级锁。Q10JVM 对 synchronized 做了哪些优化答案① 锁升级偏向→轻量→重量② 自适应自旋锁JVM 动态调整自旋次数③ 锁消除逃逸分析移除无竞争锁④ 锁粗化合并连续加锁/解锁。三、JMM与volatile篇4题Q11JMMJava 内存模型是什么和 JVM 内存结构有什么区别答案JMM 是 Java 语言规范定义的内存访问规则解决多线程间“数据何时可见、执行顺序如何保证”的问题。JVM 内存结构解决“数据存在哪里”——堆、栈、方法区等。两者完全不同——一个管“怎么看见”一个管“存哪里”。加分JMM 的抽象结构包括主内存共享变量存储和每个线程私有的工作内存CPU 缓存抽象。Q12volatile 能保证原子性吗为什么答案不能。volatile 只保证单次读写操作的可见性但count包含“读-改-写”三个字节码步骤volatile 无法保证这三步不被其他线程打断。Q13volatile 如何保证可见性答案volatile 写操作强制将本地内存修改刷新到主内存volatile 读操作强制使本地内存副本失效从主内存重新读取。通过内存屏障StoreStore、StoreLoad、LoadLoad、LoadStore禁止指令重排序配合 MESI 缓存一致性协议实现跨线程可见性。Q14happens-before 规则有哪些答案核心规则包括程序顺序规则同一线程内前面的操作 happens-before 后面的、volatile 变量规则volatile 写 happens-before 后续对同一 volatile 的读、锁规则解锁 happens-before 后续加锁、传递性A happens-before BB happens-before C → A happens-before C。四、AQS与并发工具篇5题Q15AQS 是什么核心思想是什么答案AQSAbstractQueuedSynchronizer是 JUC 并发包的底层基石。核心三要素①volatile int state同步状态② CLH 双向 FIFO 队列等待线程队列③ 模板方法模式子类只需实现tryAcquire/tryRelease等方法。加分AQS 提供独占模式ReentrantLock和共享模式Semaphore、CountDownLatch。Q16ReentrantLock 的可重入性是如何实现的答案在tryAcquire中先检查 state0 则 CAS 抢锁大于 0 则判断当前线程是否已是锁持有者是则state重入否则获取失败。解锁时每次state--归零才释放锁。Q17CountDownLatch 和 Semaphore 的区别答案CountDownLatch 是倒计数门栓countDown()减 1归零唤醒所有等待线程计数器不能重置。Semaphore 是信号量acquire()减 1release()加 1许可数可动态增减用于限流控制并发访问数。Q18AQS 的共享模式和独占模式有什么区别答案独占模式释放锁时只唤醒一个后继线程。共享模式释放锁时会传播唤醒——如果当前节点状态为PROPAGATE会继续唤醒后继节点让多个等待线程同时获取共享锁。Q19Condition 的await()和signal()与wait()/notify()有什么区别答案wait()/notify()是 Object 方法与 synchronized 配合只能有一个等待队列。Condition是 ReentrantLock 配套支持多个 Condition多个等待队列支持中断响应和超时等待。五、线程池篇5题Q20ThreadPoolExecutor 的七大参数是什么答案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使用有界队列 明确拒绝策略。Q24corePoolSize 如何设置答案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题Q28CompletableFuture 和 Future 的区别答案Future 只能通过get()阻塞获取结果无法链式调用、无法组合多个任务、异常处理困难。CompletableFuture 支持链式调用thenApply、任务组合thenCombine、allOf、非阻塞回调whenComplete和优雅异常处理exceptionally。Q29thenApply和thenCompose的区别答案thenApply是同步转换返回转换后的值不创建新的 CompletableFuture。thenCompose连接两个有依赖关系的异步任务返回新的 CompletableFuture避免CompletableFutureCompletableFutureU嵌套。Q30CAS 的三要素是什么有哪些优缺点答案三要素内存位置 V、预期值 A、新值 B。优点无锁、无上下文切换、低竞争下性能极高。缺点① ABA 问题② 高冲突下自旋消耗 CPU③ 只能保证单个变量的原子性。加分CAS 是 CPU 原子指令x86 下为lock cmpxchg原子性由硬件保证。Q31什么是 ABA 问题如何解决答案变量从 A 变成 B 又变回 ACAS 误判为未被修改。解决方案用AtomicStampedReference版本号或AtomicMarkableReference标记位。加分ABA 问题的危害在于中间状态丢失在无锁栈、无锁队列中可能导致链表断裂。Q32LongAdder 为什么比 AtomicLong 快答案LongAdder 采用分段累加设计低并发时直接 CAS 更新 base高并发时将累加操作分散到多个 Cell 上每个线程各自累加自己的 Cell最后求和。避免所有线程竞争同一个变量。八、ThreadLocal篇3题Q33ThreadLocal 的内存泄漏是怎么产生的答案ThreadLocalMap 的 Entry 用弱引用存储 keyThreadLocal 实例用强引用存储 value。当 ThreadLocal 对象外部引用被置空后GC 回收 ThreadLocal 实例Entry.key 变为 null但 value 仍被强引用。线程存活时null-key Entry 永久残留 → 内存泄漏。加分弱引用只是“兜底”根治方法只有用完手动remove()。Q34remove()和set(null)有什么区别答案remove()从 ThreadLocalMap 中删除整个 Entrykey 和 value 都被清除彻底释放内存。set(null)只是把 value 设为 null但 Entry 本身仍存在key 仍是当前 ThreadLocal 实例Entry 不会被回收。Q35线程池中如何使用 ThreadLocal答案线程池核心线程长期存活ThreadLocalMap 一直存在。必须每次任务结束在try-finally中调用remove()否则会导致内存泄漏和脏读后续请求读到前一个请求的数据。加分跨线程池传递上下文用阿里开源的TransmittableThreadLocalTTL。九、面试官追问陷阱加分题追问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 点击关注我更新后第一时间收到推送