第4章 并发编程核心:线程池参数、锁机制、CAS与AQS
并发编程是高并发系统平台工程师面试的必考核心。本章围绕线程池参数调优、锁升级机制、CAS原理与ABA问题、AQS底层设计四大知识模块,拆解面试官考察逻辑与回答框架,结合真实项目案例,帮你掌握从原理到实战的完整表达链路。
面试问答卡片
📌 面试问题:线程池的核心参数有哪些?如何合理设置线程池大小?
🎯 考察点:是否理解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,新任务会如何处理?线程池中的线程如何复用?
📌 面试问题:谈谈你对 synchronized 锁升级机制的理解,偏向锁、轻量级锁、重量级锁是如何切换的?
🎯 考察点:是否理解Java对象头Mark Word结构;是否清楚锁升级的触发条件和性能差异;能否分析为何需要锁升级。
🧠 回答框架:
- 概念阶段:锁共有4种状态(无锁→偏向锁→轻量锁→重量锁),JVM默认启动偏向锁,但4s后自动开启。
- 升级触发:偏向锁:只有一个线程访问;轻量锁:有线程竞争时撤销偏向锁,使用CAS自旋;重量锁:自旋超过阈值或线程数超过CPU核一半,转为操作系统互斥。
- 底层原理:Mark Word中存储锁标志位(01→01(偏向)→00→10)。
📝 示例回答:“synchronized锁升级是JVM自适应的优化。初期使用偏向锁,仅记录线程ID;当另一线程竞争时,撤销偏向锁升级为轻量锁,通过CAS获取锁;若CAS自旋超过阈值,则膨胀为重量锁,阻塞线程。核心是减少不必要的上下文切换。”
🔍 可能追问:为什么JDK15默认禁用了偏向锁?轻量锁自旋次数如何控制?怎么查看当前锁状态?
-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中共享锁与独占锁的实现差异?
项目案例模板:高并发订单处理系统
项目名称:亿级流量电商订单中心
业务背景: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。”