JUC 实际业务高频面试题浅谈(由浅入深+底层原理+业务落地)
整体学习顺序:线程基础 → 并发三大特性/JMM → 内置锁synchronized → 显式锁Lock/AQS → 同步工具类 → 线程池 → 并发容器 → CompletableFuture异步编排 → ThreadLocal → 真实业务并发场景实战
一、Java线程基础(JUC前置必问)
1. 进程和线程的区别?业务中为什么要用多线程?
面试答案
- 进程:资源分配最小单位,独立内存空间,进程间隔离性强、通信成本高;
- 线程:调度执行最小单位,共享进程堆/方法区,只独有栈/程序计数器,切换成本极低;
- 多线程适用:IO密集型(接口调用、文件读写、数据库查询)、批量任务处理、异步解耦、多任务并行;不适用:CPU密集型且计算简单(线程切换反而损耗性能)。
底层原理
线程是轻量级进程,OS内核调度,Java线程1:1映射系统内核线程。
业务场景
电商批量订单推送、批量导入Excel、微服务多接口并行查询、日志异步打印。
2. 线程有哪些生命周期?状态如何流转?
6大状态 :NEW新建 → RUNNABLE就绪运行 → BLOCKED阻塞 → WAITING无限等待 → TIMED_WAITING限时等待 → TERMINATED终止。
流转核心:
- 抢锁失败→BLOCKED;
- wait()/join()/LockSupport.park()→WAITING;
- sleep()/wait(time)→TIMED_WAITING。
业务落地
排查线上线程阻塞、死锁、接口超时,必看线程堆栈状态。
3. start() 和 run() 有什么区别?为什么不能直接调用run()?
start():向OS申请创建内核线程,由JVM调度执行run(),真正开启新线程;- 直接调用
run():只是普通方法调用,仍在当前主线程执行,无多线程效果。
二、并发三大特性 & JMM内存模型(JUC核心基石)
1. 并发三大特性是什么?JMM如何保障?
三大特性
- 原子性:操作不可分割,要么全执行要么不执行;
- 可见性:一个线程修改共享变量,其他线程立刻感知;
- 有序性:程序按代码顺序执行,禁止指令重排序。
JMM保障方案
- 原子性:synchronized、Lock、Atomic原子类;
- 可见性:volatile、synchronized、final;
- 有序性:volatile 禁止重排序、内存屏障。
2. volatile 关键字作用?能保证原子性吗?底层原理?
面试答案
- 保证可见性 、有序性 ,不保证原子性;
- 禁止指令重排序,适用于状态标记、单例DCL。
底层原理
volatile 底层通过 内存屏障 + 缓存一致性协议(MESI)实现:
- 写屏障:强制刷新工作缓存到主内存;
- 读屏障:强制从主内存加载最新数据。
为什么不能保证原子性
i++ 是读-改-写三步操作,volatile 只能保证每次读到最新值,无法锁住三步操作,多线程下仍会覆盖。
业务场景
- 秒杀系统标记秒杀结束状态;
- 服务启停开关、配置动态刷新标记;
- DCL双重检查锁单例。
3. 什么是指令重排序?DCL单例为什么必须加volatile?
指令重排序:CPU/JVM为了优化执行效率,打乱代码执行顺序,单线程无影响,多线程会出bug。
DCL漏洞原因
new 对象 底层三步:分配内存→初始化对象→赋值引用;
重排序后可能变成:分配内存→赋值引用→初始化对象,其他线程拿到半初始化空对象 ,引发空指针。
volatile 禁止构造方法和赋值之间重排序,彻底解决该问题。
三、synchronized 内置锁(面试必问,业务最常用)
1. synchronized 三种用法及区别?
- 修饰实例方法:锁当前对象this;
- 修饰静态方法:锁类对象Class,全局唯一;
- 修饰代码块:锁自定义对象,粒度更细。
业务选型
优先用代码块锁,缩小锁粒度,提升并发性能。
2. synchronized 底层锁升级原理?偏向锁/轻量级/重量级
锁升级顺序 :偏向锁 → 轻量级锁(自旋)→ 重量级锁 ,只能升级不能降级。
- 偏向锁:单线程无竞争,标记线程ID,无加锁开销;
- 轻量级锁:轻微竞争,CAS自旋抢锁,不阻塞内核;
- 重量级锁:竞争激烈,自旋失败,进入OS内核阻塞,开销最大。
原理核心
锁信息存在 对象头MarkWord 中,JDK1.6之后自适应自旋、锁优化大幅降低开销。
业务优化
尽量减少锁竞争、缩小锁粒度,避免直接升级为重量级锁导致接口卡顿。
3. synchronized 和 ReentrantLock 区别?业务怎么选型?
| 特性 | synchronized | ReentrantLock |
|---|---|---|
| 实现 | JVM底层原生 | 基于AQS代码实现 |
| 可中断 | 不可中断 | 可中断lockInterruptibly |
| 超时抢锁 | 无 | tryLock(time) 超时放弃 |
| 公平锁 | 非公平默认 | 支持公平/非公平 |
| 条件队列 | 1个等待队列 | 多个Condition条件队列 |
| 自动释放 | 自动释放 | 必须finally手动unlock |
业务选型
- 简单同步、代码简洁:用 synchronized;
- 需要超时抢锁、可中断、读写分离、多条件等待:用 ReentrantLock。
四、Lock & AQS 核心原理(JUC基石,进阶必问)
1. ReentrantLock 可重入原理?公平锁和非公平锁业务区别
可重入原理
AQS 有 state 状态变量:
- 加锁:state+1;
- 重入:再次加锁state继续+1;
- 释放锁:state-1,减到0才真正释放。
公平锁vs非公平锁
- 非公平锁:上来直接CAS抢锁,不排队,吞吐量高,默认使用;
- 公平锁:必须排队按顺序抢锁,避免线程饥饿,吞吐量低。
业务场景
- 高并发秒杀、接口限流:非公平锁;
- 订单排队处理、需保证先后顺序:公平锁。
2. AQS 是什么?核心设计思想
AQS(抽象队列同步器)
JUC所有锁/工具类的底层基石,核心:
- volatile int state:同步状态(锁标记、资源计数);
- 双向CLH阻塞队列:抢锁失败的线程入队阻塞;
- 模板方法:自定义锁只需实现 tryAcquire/tryRelease。
基于AQS的组件
ReentrantLock、ReadWriteLock、CountDownLatch、Semaphore、CyclicBarrier、线程池。
3. ReadWriteLock 读写锁原理&业务场景
原理
读锁共享、写锁独占:
- 多线程可以同时加读锁;
- 有读锁时写锁阻塞,有写锁时读写都阻塞。
业务场景
读多写少 场景:本地缓存配置、商品基础信息、字典数据、系统参数。
极高提升并发读性能,比独占锁强很多。
五、三大同步工具类(业务高频使用)
1. CountDownLatch、CyclicBarrier、Semaphore 区别及业务场景
(1)CountDownLatch 倒计时门闩
原理 :初始化计数器,线程调用countDown()-1,await()等待计数器归0才放行;一次性使用,不能重置 。
业务场景
- 微服务多接口并行查询,所有接口返回后再汇总结果;
- 批量多线程处理任务,等待所有子线程执行完毕再主线程收尾。
(2)CyclicBarrier 循环栅栏
原理 :一组线程互相等待,凑齐指定数量同时放行;可循环重复使用 。
业务场景
- 多人协作任务,所有人准备好再同时开始;
- 分批次批量处理数据,每批凑齐线程再执行。
(3)Semaphore 信号量
原理 :控制同时访问资源的线程数量,做限流 ;acquire获取许可,release释放许可。
业务场景
- 接口限流、第三方接口调用限流、数据库连接池控制并发;
- 秒杀系统限制瞬时请求并发数。
六、线程池(面试重中之重,业务必用)
1. 为什么禁止用 Executors 创建线程池?
- FixedThreadPool/ SingleThreadPool:无界队列,任务过多堆积引发OOM;
- CachedThreadPool:最大线程无上限,无限创建线程,耗尽CPU内存。
业务规范
必须通过 ThreadPoolExecutor 手动指定7大参数,自定义线程池。
2. 线程池七大核心参数详解
- corePoolSize:核心线程数(常驻不回收)
- maximumPoolSize:最大线程数
- keepAliveTime:空闲线程超时时间
- unit:时间单位
- workQueue:任务阻塞队列
- threadFactory:线程工厂(自定义线程名,便于排查)
- handler:拒绝策略
3. 核心线程数怎么配置?CPU密集/IO密集
公式
- CPU密集型(计算多):核心线程数 = CPU核心数 + 1
- IO密集型(数据库/接口等待多):核心线程数 = CPU核心数 * 2 ~ 2倍
业务实战
业务系统90%都是IO密集型,直接设为CPU核心数2倍即可。
4. 四种拒绝策略原理及业务适用
- AbortPolicy:直接抛异常(默认)→ 核心业务,不允许丢任务;
- CallerRunsPolicy:主线程自己执行→ 非核心业务,不抛异常不丢任务;
- DiscardPolicy:直接丢弃新任务→ 允许丢失的日志、统计任务;
- DiscardOldestPolicy:丢弃队列最旧任务→ 秒杀排队、订单排队。
5. 线程池执行流程
- 来了任务,核心线程数未满 → 创建核心线程执行;
- 核心线程已满 → 任务进入阻塞队列;
- 队列已满 → 创建非核心线程直到最大线程数;
- 达到最大线程数+队列满 → 触发拒绝策略。
6. 业务中线程池如何避免OOM和内存泄漏
- 自定义有界队列,禁止无界队列;
- 合理设置最大线程数,不超限;
- 使用自定义线程工厂,设置线程名、守护线程;
- 任务内部捕获异常,避免线程莫名退出;
- 定时监控线程池活跃线程、队列堆积数量。
七、JUC并发容器(替代普通集合线程不安全)
1. ArrayList 为什么线程不安全?
多线程同时add时,会出现数组越界、元素覆盖、数据丢失,底层无任何同步控制。
2. ConcurrentHashMap JDK7和JDK8底层原理
JDK7 :分段锁Segment,分成16个分段,每个分段独立加锁,并发度16;
JDK8 :取消分段锁,数组+链表+红黑树 ,CAS+synchronized 锁链表头节点,粒度更细、并发更高。
业务场景
全局本地缓存、配置存储、高并发键值存储,优先使用ConcurrentHashMap。
3. CopyOnWriteArrayList 原理&适用场景
原理 :写时复制,修改时复制新数组,修改完替换原数组;读无锁、写加锁 。
特点 :读性能极高,写性能差,占用内存。
业务场景 :读多写少,白名单、配置列表、权限角色列表。
4. BlockingQueue 阻塞队列业务用途
核心:生产者消费者模型
业务落地:本地消息缓冲、订单异步处理、日志队列、秒杀请求排队。
八、CompletableFuture(业务异步编排必问)
1. CompletableFuture 相比于 Future 优势
- Future 只能阻塞get()获取结果,无法回调、无法编排多任务;
- CompletableFuture 支持异步回调、串行执行、并行组合、异常处理,是业务异步首选。
2. 常用核心API业务场景
supplyAsync/runAsync:开启异步任务;thenApply/thenAccept:任务串行依赖;thenCombine/allOf:多任务并行执行;exceptionally:异步任务异常兜底。
真实业务
电商订单详情页:同时查用户信息、商品信息、库存、优惠券,并行调用,把接口响应时间从500ms降到150ms。
九、ThreadLocal 原理&业务深坑
1. ThreadLocal 底层原理
每个Thread内部有 ThreadLocalMap ,key是ThreadLocal(弱引用),value是存储的业务对象。
特点:线程私有,数据隔离,线程间互不干扰。
2. ThreadLocal 为什么会内存泄漏?怎么解决?
泄漏原因
key是弱引用会被GC回收,但value是强引用,线程不销毁(线程池),value永远无法回收。
解决方案
使用完必须手动调用 remove() ;
业务拦截器结尾统一清理ThreadLocal。
3. 业务使用场景
- 用户登录上下文透传(不用每一层接口传用户ID);
- 权限、租户ID全局透传;
- 事务上下文、分页参数全局封装。
十、真实业务经典并发综合面试题
1. 秒杀系统如何用JUC防超卖+限流?
- 限流:Semaphore 控制瞬时并发;
- 防超卖:分布式锁Redisson + 本地ReentrantLock 双层锁;
- 库存标记:volatile 标记秒杀结束;
- 异步削峰:BlockingQueue 排队处理秒杀请求。
2. 批量导入几十万数据怎么用JUC优化?
线程池 + CountDownLatch 拆分分片,多线程并行入库,执行完毕主线程返回结果,比单线程快数倍。
3. 本地配置缓存怎么优化读写性能?
用 ReadWriteLock 读写锁,读多共享、写独占,兼顾并发读安全和性能。
4. 微服务多接口聚合查询怎么优化RT?
用 CompletableFuture.allOf 并行调用多个远程接口,等待全部完成后组装返回,大幅降低接口耗时。