第4章 并发编程核心:线程池参数、锁机制、CAS与AQS

并发编程是高并发系统平台工程师面试的必考核心。本章围绕线程池参数调优、锁升级机制、CAS原理与ABA问题、AQS底层设计四大知识模块,拆解面试官考察逻辑与回答框架,结合真实项目案例,帮你掌握从原理到实战的完整表达链路。

并发编程核心 线程池参数 corePoolSize / maxPoolSize workQueue / RejectedHandler 锁机制 偏向锁 → 轻量锁 → 重量锁 synchronized 升级路径 CAS原理 Unsafe + 自旋 ABA问题解决 AQS框架 CLH队列 + state 共享/独占模式 面试回答框架:先讲定义 → 核心参数/流程 → 底层原理 → 生产实战调优 → 可能引发的坑 加分点:结合JDK源码(如ThreadPoolExecutor.execute()/Unsafe.compareAndSwapInt())说明机制

面试问答卡片

📌 面试问题:线程池的核心参数有哪些?如何合理设置线程池大小?

🎯 考察点:是否理解ThreadPoolExecutor构造参数含义,能否根据CPU/IO密集型场景进行调优,是否了解workQueue与拒绝策略的配合。

🧠 回答框架:

步骤内容
1. 参数定义corePoolSize / maxPoolSize / keepAliveTime / unit / workQueue / threadFactory / RejectedExecutionHandler
2. 场景区分CPU密集型:core = N+1;IO密集型:core = 2N 或 N/(1-阻塞系数)
3. 队列与拒绝选有界队列避免OOM,拒绝策略建议CallerRunsPolicy(降级)或自定义

📝 示例回答:“线程池参数包括corePoolSize、maxPoolSize等。以IO密集型业务为例,我通常设置corePoolSize为服务器CPU核数的2倍,maxPoolSize为core的2~3倍,队列使用ArrayBlockingQueue,拒绝策略选CallerRunsPolicy。同时通过动态监控调整核心线程数。”

🔍 可能追问:如果队列满了但线程数未达到maxPoolSize,新任务会如何处理?线程池中的线程如何复用?

⚠️ 避坑提醒:不要只说理论参数,要结合生产监控(如CPU使用率、线程活跃数、拒绝次数)说明动态调优策略。避免无界队列,否则maxPoolSize形同虚设。

📌 面试问题:谈谈你对 synchronized 锁升级机制的理解,偏向锁、轻量级锁、重量级锁是如何切换的?

🎯 考察点:是否理解Java对象头Mark Word结构;是否清楚锁升级的触发条件和性能差异;能否分析为何需要锁升级。

🧠 回答框架:

  1. 概念阶段:锁共有4种状态(无锁→偏向锁→轻量锁→重量锁),JVM默认启动偏向锁,但4s后自动开启。
  2. 升级触发:偏向锁:只有一个线程访问;轻量锁:有线程竞争时撤销偏向锁,使用CAS自旋;重量锁:自旋超过阈值或线程数超过CPU核一半,转为操作系统互斥。
  3. 底层原理:Mark Word中存储锁标志位(01→01(偏向)→00→10)。

📝 示例回答:“synchronized锁升级是JVM自适应的优化。初期使用偏向锁,仅记录线程ID;当另一线程竞争时,撤销偏向锁升级为轻量锁,通过CAS获取锁;若CAS自旋超过阈值,则膨胀为重量锁,阻塞线程。核心是减少不必要的上下文切换。”

🔍 可能追问:为什么JDK15默认禁用了偏向锁?轻量锁自旋次数如何控制?怎么查看当前锁状态?

⚠️ 避坑提醒:不要混淆“锁升级”与“锁膨胀”。回答时要有时间线:偏向锁→轻量锁→重量锁是单向不可逆。可补充JVM参数:-XX:-UseBiasedLocking来禁用偏向锁。

📌 面试问题:CAS原理是什么?如何解决ABA问题?AQS与CAS有什么关系?

🎯 考察点:是否理解Unsafe类中compareAndSwapInt的调用过程;是否知道ABA问题的危害及解决方案;能否串联CAS与AQS(如ReentrantLock的加锁流程)。

🧠 回答框架:

维度关键点
CAS原理Unsafe类提供硬件级别的原子操作,循环比较并交换,失败则自旋
ABA问题变量值从A→B→A,CAS误认为未变化;解决:增加版本号(AtomicStampedReference)
AQS依赖AQS使用CAS更新state状态,操作CLH等待队列,提供独占/共享锁模板

📝 示例回答:“CAS基于Unsafe实现,通过CPU的cmpxchg指令完成。ABA问题常见于栈或队列操作,我使用AtomicStampedReference添加时间戳解决。AQS底层依赖CAS操作state字段,ReentrantLock的lock()方法先尝试CAS设置state=1,失败则进入CLH队列挂起。”

🔍 可能追问:CAS的缺点有哪些?AtomicInteger底层如何保证线程安全?AQS中共享锁与独占锁的实现差异?

⚠️ 避坑提醒:不要只背概念,要落地到实际应用:比如ConcurrentHashMap的put流程、LongAdder如何减少CAS竞争。提到ABA时一定要说明危害场景(如链表节点复用)而非笼统说“值被改变”。

项目案例模板:高并发订单处理系统

项目名称:亿级流量电商订单中心

业务背景:618大促期间,订单创建峰值200w+ QPS,需保证库存扣减与订单生成的一致性,同时避免数据库被打垮。

规模指标:1000+台服务器,订单表月增10亿行,接口TP99延迟<50ms。

技术栈:Spring Boot + Netty + Redis + RocketMQ + ShardingSphere + Nacos

核心难点:

  • 高并发下单导致库存DB热点行锁争抢 → 使用Redis Lua脚本预扣库存,异步消化。
  • 订单去重与幂等 → 基于CAS(Redis SETNX)实现订单号防重。
  • 线程池隔离 → 为下单、退款、查询分配独立线程池,防止相互影响。

面试高频追问:订单扣库存时如何保证最终一致性?如果Redis宕机,库存扣减如何补偿?线程池参数如何根据压测结果动态调整?

可迁移回答话术:“在我的项目中,我们采用CAS+版本号解决库存扣减的ABA问题。具体实现:库存扣减时使用Redis的EVAL执行Lua脚本,先检查当前版本号是否匹配,再执行DECR。订单生成线程池使用动态核心线程+CallerRunsPolicy降级,并通过监控活跃线程数/队列积压调整corePoolSize。”