跳转至

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 还有聚簇索引概念:数据行按主键顺序存在聚簇索引叶子节点,主键索引即数据;二级索引叶子节点存主键值,查到后需回表。若查询列被索引完全覆盖则无需回表,称为覆盖索引EXPLAINUsing index)。

面试官追问 / 红旗
  • 追问:什么是回表?什么是覆盖索引?(二级索引→主键→聚簇索引取行;查询列被索引覆盖则免回表)
  • 追问:为什么主键建议自增?(顺序插入避免页分裂,写入性能好)
  • 追问:联合索引的最左前缀原则?
  • 红旗:说"B 树和 B+ 树差不多";只说"快"而说不出扇出/树高/范围扫描的具体原因;不知道回表概念。

Q4: Redis 怎么实现分布式锁?用 setnx 有什么问题?

基础 | Java后端 | 📖 相关讲解

考察点:能否识别 setnx 原始方案的多个正确性缺陷,并知道现网标准做法。

参考答案

朴素分布式锁用 SETNX(Set if Not eXists)实现,但存在一系列问题:

问题 成因 解法
锁非原子 SETNXEXPIRE 两步,中间宕机锁永不释放 SET key value NX EX 30 原子设值+过期
误删他人锁 业务超时锁自动释放,他人抢到,自己执行完误删 value 存唯一标识,删除前 Lua 脚本校验
锁过期业务仍在跑 业务执行慢,锁已过期但业务没结束 看门狗续期
主从切换丢锁 master 加锁未同步到 slave 即宕机 Redlock 多实例过半数

生产标准做法是用 RedissonRLock:它封装了原子加锁、看门狗自动续期、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 用滑动窗口统计。