Java 后端工程化 · 基础¶
覆盖线程池、JVM 内存、MySQL 索引、Redis 锁、消息队列、CAP、熔断降级等 Java 后端工程师的高频基础题。
Q1: 线程池的 7 个核心参数是什么?4 种拒绝策略分别在什么场景用?¶
基础 | Java后端 | 📖 相关讲解
考察点:能否准确说出 7 参数、拒绝策略的行为差异,以及任务提交后的执行顺序(核心→队列→非核心→拒绝)。
参考答案
ThreadPoolExecutor 的 7 个参数:
| 参数 | 含义 |
|---|---|
corePoolSize |
核心线程数 |
maximumPoolSize |
最大线程数 |
keepAliveTime |
非核心线程空闲存活时间 |
unit |
时间单位 |
workQueue |
任务队列(必须用有界队列) |
threadFactory |
线程工厂(自定义命名便于排查) |
handler |
拒绝策略 |
任务提交后的判断顺序很关键:先看核心线程是否未满 → 未满则创建核心线程;核心满了 → 进队列;队列满了 → 创建非核心线程到 maximumPoolSize;再满 → 触发拒绝策略。
4 种拒绝策略:AbortPolicy(默认,抛异常)、CallerRunsPolicy(提交线程自己执行,做背压)、DiscardPolicy(静默丢弃)、DiscardOldestPolicy(丢最老任务)。
场景上:交易核心链路用 CallerRunsPolicy 做优雅背压,让上游自动降速;可丢弃的日志/埋点用 DiscardPolicy;只关心最新值的行情用 DiscardOldestPolicy;需要显式感知并兜底的用 AbortPolicy 捕获异常做降级。
生产环境必须显式 new ThreadPoolExecutor 并用有界队列,禁用 Executors.newFixedThreadPool/newCachedThreadPool——前者无界队列、后者最大线程数 Integer.MAX_VALUE,都是 OOM 定时炸弹。
面试官追问 / 红旗
- 追问:核心线程满了之后,新任务是先入队还是先扩到最大线程数?(答错是高频红旗,正确是先入队)
- 追问:CPU 密集型和 IO 密集型 corePoolSize 怎么设?(CPU 密集 ≈ N+1,IO 密集 ≈ 2N~10N)
- 追问:为什么禁用
Executors?(无界队列/无界线程数的 OOM 风险) - 红旗:把执行顺序答反成"先扩到最大线程数再入队";说不出有界队列的重要性;提到
Executors当生产默认选择。
Q2: JVM 运行时数据区有哪些?哪些是线程私有的?¶
基础 | Java后端 | 📖 相关讲解
考察点:能否准确划分线程私有与共享区域,理解各区域存储内容与异常类型。
参考答案
JVM 运行时数据区分为:
| 区域 | 线程私有 | 存储内容 | 异常 |
|---|---|---|---|
| 程序计数器 (PC) | 是 | 当前线程字节码行号 | 唯一不 OOM |
| 虚拟机栈 | 是 | 栈帧、局部变量、操作数栈 | StackOverflowError / OOM |
| 本地方法栈 | 是 | Native 方法 | StackOverflowError / OOM |
| 堆 (Heap) | 否(共享) | 对象实例、数组 | OOM |
| 方法区/元空间 | 否(共享) | 类元信息、常量池、静态变量 | OOM: Metaspace |
| 直接内存 | 否(共享) | NIO Buffer | OOM: Direct buffer |
线程私有的是程序计数器、虚拟机栈、本地方法栈;共享的是堆、方法区(JDK8 后为元空间,元空间在本地内存)、直接内存。
程序计数器是唯一不会 OOM 的区域,它记录当前线程执行到的字节码行号,用于线程切换后恢复。栈溢出常见于深递归或栈帧过大;堆 OOM 是生产最常见的,多为内存泄漏或大对象。
面试官追问 / 红旗
- 追问:JDK8 把永久代换成了什么?为什么?(元空间,移到本地内存,避免永久代 OOM 且难调)
- 追问:栈会 OOM 吗?什么场景?(深递归导致 StackOverflowError;不断开线程可 OOM)
- 追问:程序计数器为什么线程私有?(多线程切换后能恢复执行位置)
- 红旗:把方法区/堆答成线程私有;说不出程序计数器不会 OOM;混淆永久代与元空间。
Q3: InnoDB 的索引是什么数据结构?为什么用 B+ 树而不是 B 树或 Hash?¶
基础 | Java后端 | 📖 相关讲解
考察点:理解 B+ 树的结构特征,能从"磁盘 IO、范围查询、扇出"角度论证为何 B+ 树胜出。
参考答案
InnoDB 索引数据结构是 B+ 树。它的三个关键特征:① 非叶子节点只存索引键不存数据,扇出大、树更矮;② 数据全在叶子节点,且叶子节点间用双向链表相连;③ 三层 B+ 树可支撑千万级行,每次查询约 3 次磁盘 IO。
为什么不用其他结构:
| 结构 | 劣势 |
|---|---|
| Hash | 不支持范围查询与排序 |
| 红黑树/二叉树 | 树高随数据线性增长,磁盘 IO 太多 |
| B 树 | 非叶子节点也存数据,扇出小、树更高;范围查询需中序回溯 |
| B+ 树 | 扇出大、树矮、叶子链表高效支持范围扫描 |
InnoDB 还有聚簇索引概念:数据行按主键顺序存在聚簇索引叶子节点,主键索引即数据;二级索引叶子节点存主键值,查到后需回表。若查询列被索引完全覆盖则无需回表,称为覆盖索引(EXPLAIN 的 Using index)。
面试官追问 / 红旗
- 追问:什么是回表?什么是覆盖索引?(二级索引→主键→聚簇索引取行;查询列被索引覆盖则免回表)
- 追问:为什么主键建议自增?(顺序插入避免页分裂,写入性能好)
- 追问:联合索引的最左前缀原则?
- 红旗:说"B 树和 B+ 树差不多";只说"快"而说不出扇出/树高/范围扫描的具体原因;不知道回表概念。
Q4: Redis 怎么实现分布式锁?用 setnx 有什么问题?¶
基础 | Java后端 | 📖 相关讲解
考察点:能否识别 setnx 原始方案的多个正确性缺陷,并知道现网标准做法。
参考答案
朴素分布式锁用 SETNX(Set if Not eXists)实现,但存在一系列问题:
| 问题 | 成因 | 解法 |
|---|---|---|
| 锁非原子 | SETNX 与 EXPIRE 两步,中间宕机锁永不释放 |
SET key value NX EX 30 原子设值+过期 |
| 误删他人锁 | 业务超时锁自动释放,他人抢到,自己执行完误删 | value 存唯一标识,删除前 Lua 脚本校验 |
| 锁过期业务仍在跑 | 业务执行慢,锁已过期但业务没结束 | 看门狗续期 |
| 主从切换丢锁 | master 加锁未同步到 slave 即宕机 | Redlock 多实例过半数 |
生产标准做法是用 Redisson 的 RLock:它封装了原子加锁、看门狗自动续期、Lua 脚本安全释放、可重入。加锁时不指定 leaseTime 则默认 30 秒并启动看门狗,每 10 秒续期一次,避免业务没跑完锁过期。
工程上要避免手撸 setnx,直接用 Redisson;金融/资金类对正确性要求极高的场景,Redis 锁的 Redlock 仍有争议,应走 DB 行锁或 Zookeeper/etcd 共识锁。
面试官追问 / 红旗
- 追问:看门狗续期原理?依赖什么?(后台线程定时续期,依赖客户端进程存活)
- 追问:释放锁为什么用 Lua 脚本?(保证"判断+删除"原子性,避免误删)
- 追问:Redis 锁能用于扣款吗?(不能,正确性不足,走 DB/共识锁/TCC)
- 红旗:只答"setnx 加过期时间"就认为没问题,没识别误删/续期/主从丢锁;说不出为什么用 Redisson。
Q5: Kafka 和 RocketMQ 的核心区别是什么?¶
基础 | Java后端 | 📖 相关讲解
考察点:能否从设计哲学、吞吐、事务消息、顺序消息等维度做对比,并给出选型判断。
参考答案
Kafka 与 RocketMQ 的核心区别:
| 维度 | Kafka | RocketMQ |
|---|---|---|
| 设计定位 | 高吞吐日志/流处理 | 电商交易场景 |
| 吞吐 | 极高(顺序写+零拷贝) | 高,略低 |
| 事务消息 | 生产者事务(精确一次) | 原生业务级事务消息(半消息+回查) |
| 顺序消息 | 分区内有序 | 支持全局/分区顺序,电商成熟 |
| 延时消息 | 需自建 | 原生多级延时 |
| 消息回溯 | 按 offset/时间戳 | 按时间回溯 |
| 适用 | 日志、大数据流、事件溯源 | 交易、订单、金融、事务消息 |
一句话总结:Kafka 为高吞吐流式日志而生,RocketMQ 为电商交易、事务消息、延时消息而生。
选型判断:需要业务级事务消息(下单+发消息原子)、可靠顺序消费、延时消息的电商交易场景选 RocketMQ;纯高吞吐数据管道(日志、埋点、大数据)选 Kafka。报告项目交易链路用 RocketMQ 正是因为需要事务消息保证下单与库存/支付消息的原子性。
面试官追问 / 红旗
- 追问:RocketMQ 事务消息的两阶段流程?(半消息→本地事务→提交/回滚→回查)
- 追问:Kafka 怎么保证消息不丢?(生产 ACK + 副本 + 消费手动提交 offset)
- 追问:顺序消息为什么牺牲吞吐?(单队列单消费者)
- 红旗:只说"Kafka 吞吐高"而无对比维度;不知道 RocketMQ 有事务消息;把两者说成完全等价。
Q6: 什么是 CAP 定理?互联网业务一般怎么取舍?¶
基础 | Java后端 | 📖 相关讲解
考察点:准确表述 CAP,理解 P 必选的现实,能给出互联网业务的取舍逻辑。
参考答案
CAP 定理:分布式系统在 C(一致性 Consistency)、A(可用性 Availability)、P(分区容错 Partition Tolerance) 三者中,发生网络分区时只能满足其中两个。
关键认知是 P 是必选的——网络分区(断网、丢包、节点宕机)在分布式环境中必然发生,无法避免。所以本质是在 CP 与 AP 间二选一:
- CP:分区时宁可拒绝服务也要保证一致(Zookeeper、etcd、Redis Redlock 思路)。
- AP:分区时保证可用,允许短期不一致,事后修复(Nacos 默认 AP、Eureka、大多数互联网业务)。
互联网业务一般选 AP + 最终一致性:因为可用性直接关系到 SLA 与收入(电商下单不能因为个别节点分区就整体不可用),一致性用补偿、消息、对账来最终达成。这就是 BASE 理论(Basically Available、Soft state、Eventually consistent)的实践。
与之对应,ACID 是单机/同库事务的强一致保证。所以金融核心(如银行记账)可能选 CP/强一致,而交易大促的订单系统选 AP+最终一致,配合每日对账兜底。
面试官追问 / 红旗
- 追问:为什么 P 必选?(网络分区无法避免)
- 追问:BASE 是什么?和 CAP 什么关系?(BASE 是 AP 的工程实践)
- 追问:Nacos 是 CP 还是 AP?(可切换,默认 AP)
- 红旗:说"三者都要满足";不理解 P 必选;把 ACID 和 CAP 混为一谈;说不出互联网选 AP 的理由。
Q7: 什么是熔断降级?Sentinel 的核心概念有哪些?¶
基础 | Java后端 | 📖 相关讲解
考察点:理解熔断降级防止雪崩的机制,掌握 Sentinel 的资源/规则/槽位链/熔断状态机概念。
参考答案
熔断降级是防止故障扩散的稳定性手段:当下游服务出现异常或响应变慢时,主动"熔断"对其的调用(快速失败),并"降级"走兜底逻辑(默认值、缓存、简化流程),避免把上游线程池拖垮导致级联雪崩。
熔断的三个状态机:CLOSED(正常放行)→ OPEN(熔断,请求快速失败)→ HALF_OPEN(半开,放少量请求试探下游是否恢复)。试探成功则回到 CLOSED,仍失败则继续 OPEN。
Sentinel 是阿里开源的流量治理组件,核心概念:
| 概念 | 作用 |
|---|---|
| 资源 (Resource) | 被保护的接口/代码段 |
| 规则 (Rule) | 流控/熔断/热点/系统规则 |
| 槽位链 (Slot Chain) | 统计→规则判断→降级执行的处理链 |
| 熔断策略 | 慢调用比例、异常比例、异常数 |
Sentinel 的三种熔断策略:慢调用比例(RT 超阈值比例超阈值触发)、异常比例(异常率超阈值)、异常数(异常数超阈值)。它通过滑动时间窗口统计资源调用情况,基于规则做流控与熔断。
工程上对弱依赖(第三方支付、推荐)配慢调用熔断,熔断期间走降级(缓存价/默认库存),保住主链路可用。
面试官追问 / 红旗
- 追问:熔断的三个状态怎么流转?(CLOSED→OPEN→HALF_OPEN)
- 追问:Sentinel 三种熔断策略是什么?
- 追问:熔断和限流的区别?(限流控制速率,熔断是故障隔离)
- 红旗:把熔断和限流混为一谈;说不出半开探测;不知道 Sentinel 用滑动窗口统计。