面灵AI→

小红书后端开发岗面经合集(四):AQS 与锁升级、线程池与高并发活动设计

轮次
多轮面试合集
时间
2026-09
来源
牛客网

《面试题目》

面经 01 · 小红书后端开发岗(2025-09-25)

  1. 有挑战的事情、如何做的、做了什么、具体实现与业务细节
  2. 现有 a、b、c 三个线程,需实现「a 执行完执行 b,b 执行完执行 c,c 执行完回头执行 a」的按需循环效果,该如何实现?
  3. 请说明 synchronized 锁升级的过程
  4. 轻量级锁基于 CAS 自旋实现,其默认自旋次数是多少?从轻量级锁升级到重量级锁的临界条件是什么?
  5. 轻量级锁(CAS)存在 ABA 问题,该如何解决?
  6. JUC 包中乐观锁实现(如 ReentrantLock、CountDownLatch 等)底层依赖的抽象类 AQS,其底层实现逻辑是什么?
  7. AQS 中的公平锁和非公平锁是如何实现的?
  8. 请说明线程池的核心参数,以及这些核心参数之间的关系(任务提交时的判断逻辑)
  9. 若线上有一个 MySQL 需要优化,请说明你的优化思路
  10. 使用 explain 分析 SQL 时,核心关注哪些指标?这些指标如何帮助优化?
  11. 若 explain 分析无问题、已加索引,但 MySQL 整体速度仍很慢,还有哪些优化思路和手段?
  12. InnoDB 存储引擎底层用什么数据结构存储数据和索引?
  13. B+ 树有什么特点?
  14. 一个三层的 B+ 树大概能存储多少数据?其评估思路是什么?
  15. 若 MySQL 页大小为 16K,以主键索引为例,一页大概能存储多少数据?
  16. MySQL 的 InnoDB 存储引擎支持事务,其是如何实现事务特性(ACID)的?
  17. 你在实习过程中用过 Redis 吗?用了 Redis 的哪些数据类型?解决了什么业务问题?
  18. Redis 的 string 和 list 类型,底层用的数据结构是什么?
  19. string 类型底层基于「简单动态字符串(SDS)」实现,这种数据结构的好处是什么?
  20. 若业务数据需存储在 Redis 分片集群(单个节点存不下,需多节点联合存储),Redis 是如何实现数据写入和读取的?其底层是否用哈希实现?
  21. 现有一个活动:奖品数量少(仅几百个)、参与人数多(几十万)、瞬间 QPS 高(数十万),需对该系统进行设计,具体该怎么做?设计过程中核心需要关注哪些点?

面经 02 · 小红书后端开发岗(2025-09-19)

  1. 了解个人情况,介绍自己业务
  2. struct 内存对齐
  3. 上来要求手写一个线程池
  4. 算法一:给你一个二维矩阵,上面写满了数字 0 和 1,要求你找出一个连通块,使得这个连通块内所有数字都是 0 且边界全部是 1(被 1 包围)
  5. 算法二:你是一个售票机,一开始售票机内有 0 元,有 m+n 个人来买票,每张票 50 元,其中 m 个人手上有 50 块钱,n 个人手上有 100 块钱,每个人来买票顺序不确定,求找不开钱情况的概率
  6. boost 库用过吗?std::future
  7. SIMD 等 C++ 的八股和技术栈

面经 03 · 小红书后端开发岗(2025-09-18)

  1. 你能简单解释一下什么是 Java 的内存模型(JMM)吗?它是如何保证多线程下的可见性和有序性的?
  2. 在项目中使用 SpringBoot 时,如何实现一个简单的自定义注解,并让它在某个切面中生效?
  3. 假设有一个包含百万级数据的表,你该如何通过 SQL 查询快速找到某个字段的最大值?
  4. 你了解 Java 中的 ClassLoader 吗?它是如何加载类的?你是如何避免类加载冲突的?
  5. 在分布式架构中,Nacos 作为配置中心的作用是什么?它如何帮助管理服务的配置?
  6. Redis 中的缓存穿透、缓存雪崩、缓存击穿分别是什么?你是如何在项目中处理这些问题的?
  7. 你如何在 Spring 项目中实现一个简易的异步任务执行?能举例说明一下如何用 @Async 注解实现吗?

面经 04 · 小红书后端开发岗(2025-09-14)

  1. 一张表假设有多个字段 a、b、c,建立了 abc 联合索引,where a = ? and b = ? order by c 能命中索引吗?
  2. 索引的底层原理
  3. 单例模式怎么实现?为什么要双重检测?如果使用反射机制可以破坏单例吗?
  4. MySQL 事务隔离级别介绍,你平时用的哪一个?
  5. Redis 你在什么场景使用?旁路缓存会存在脏数据吗?
  6. Redis 获取缓存耗时增加了,原因可能是什么?
  7. OOM 问题怎么处理?Java OOM 问题处理,原因可能是什么,怎么处理?
  8. 优惠券秒杀(方案描述),这里最核心的问题是什么?你是怎么避免超卖的?
  9. 线程池有哪些核心参数?为什么线程池要先使用核心线程,然后队列排队、队列满才创建非核心线程,为什么要这么设计?

面经 05 · 小红书后端开发岗(2025-09-12)

  1. 实习:缓存怎么设计的?库存扣减的逻辑具体是怎么样的?用 lua 脚本怎么实现的?lua 脚本怎么保证原子性的?并发情况下的状态变更会出现问题吗?
  2. 消息队列可靠性怎么实现的?
  3. 介绍一下 redis 的集群模式(主从加哨兵),集群模式下的 hash 槽是怎么分配的?扩展之后呢?
  4. 说一下哈希环相关的知识?
  5. 说一下 @Service 注解的具体原理?
  6. spring 事务的实现方式?失效场景?如果是标注在类的静态方法上呢?
  7. 缓存击穿、穿透、雪崩?对应的解决方案?
  8. 场景:有一个本地缓存加 Redis 加 MySQL 的三层架构,你怎么设计数据更新时的逻辑?需要注意的点有哪些?有考虑过使用本地缓存自己的更新机制吗?多实例下的本地缓存,怎么保证每个实例都能触发更新?
  9. 利用消息队列的广播机制,怎么让多个消费者能消费到同一条消息?详细说说 kafka 广播模式的底层原理
  10. 本地缓存 caffeine 的缓存更新机制和过期策略了解吗?
  11. 判断一个链表是否成环

面经 06 · 小红书后端开发岗(2025-09-12)

  1. 聊实习
  2. 了解 netty 的内存池算法吗?netty 和 nio 的关系
  3. 线程池的原理?如果不用线程池还可以怎么做?异步为什么好?
  4. jvm 和 os 的线程有什么关系?linux 中线程和进程的异同?
  5. 零拷贝的流程?有哪些系统调用?
  6. 场景题:分库分表
  7. mysql 分哪几层,各自功能是什么?mysql 的 ACID 各自由什么来保证?
  8. 算法:topk
  9. 算法:两个有序数组的中位数查找,要求 log 时间复杂度
  10. aqs 是什么?如果要实现一个 aqs,你觉得核心是什么?
  11. 有锁和无锁的区别是什么?乐观锁和悲观锁区别是什么?java 中有哪些锁?
  12. synchronized 有什么缺点?为什么内核态和用户态切换耗时长?你了解虚拟线程吗?
  13. kafka 如何保证消息不丢失?kafka 不就是暂时存储消息的吗,为什么它的消息积压也是一个问题?
  14. page cache 是什么?有什么作用?
  15. 大三的课程怎么办?最快什么时候到岗?家是哪里的?

面经 07 · 小红书后端开发岗(2025-09-01)

  1. 挑一个项目来介绍一下
  2. Java 的 GC 过程会有 Stop the World,谈谈为什么要有 STW 的机制?
  3. ZGC 垃圾回收器
  4. G1 已经很不错了,为什么还要有 ZGC 这样的垃圾回收器,为了解决什么问题?
  5. 比如一个订机票的场景,涉及多个外部系统,首先要去看有没有票,然后支付要调支付宝或者微信去付款,订完票可能过了半个小时才告诉我订票有没有成功,对于这种场景下的分布式事务,你认为怎么去处理和设计来保证一致性比较好?
  6. 基于消息传递的方案,消息可能传递失败,如何解决?
  7. 如果用消息队列,这种场景怎么做技术选型?
  8. 做题:新兵报到,指导员命令所有人按身高大小从低到高依次站好,每次从头这边开始调整,但是要求每次只能进行一次交换

《参考解析》

面经 01 / 04 / 06 · 并发与锁

  1. 锁升级、自旋与 ABA:升级路径是无锁 → 偏向锁 → 轻量级锁 → 重量级锁。偏向锁把线程 ID 写进对象头,同一线程再进入无需 CAS;出现第二个线程竞争就撤销偏向、升级为轻量级锁,竞争线程在栈上建立 Lock Record 并 CAS 抢 Mark Word,失败进入自旋;自旋到阈值(JDK 6 之后是自适应自旋,次数由前一次在同一锁上的成功率动态决定,不再是固定的 10 次)仍拿不到锁就膨胀为重量级锁,靠 monitor 与操作系统互斥量挂起。临界条件要答成「自适应自旋失败、或竞争线程数超过 CPU 核数的一半、或锁持有者长时间不释放」这类判据,而不是背一个数字。轻量级锁的 ABA 问题的落点是 CAS 只比较值不比较历史:解决手段是加版本号(AtomicStampedReference)或时间戳,也可以换成 AtomicMarkableReference 只关心「有没有被改过」。面试官更想听的是「ABA 在什么场景才会造成业务损失」——只有在「值变回原样但中间状态有意义」时才有害,比如栈的 pop 场景,纯粹计数场景通常无害。

  2. AQS 与公平性:AQS 的骨架是一个 volatile 的 state 加一个 CLH 变体的双向等待队列。以 ReentrantLock 为例,state 为 0 表示空闲、大于 0 表示被占用的重入次数;抢锁是 CAS 把 state 从 0 改成 1 并记录持有线程,失败就包装成 Node 入队,然后通过 LockSupport.park 挂起;释放时 state 减到 0,唤醒队列里的后继节点。公平与非公平的唯一差别在「入队前是否先检查队列为空」:非公平锁直接抢,抢不到再排队,所以可能出现后来的线程插队成功,吞吐更高但理论上会饿死先来者;公平锁每次先看有没有前驱,有就乖乖排队,代价是更多的上下文切换。「队列里要存什么才能自己实现一把锁」的答案是线程引用(要唤醒谁)、等待状态(区分正常等待、取消、条件等待)、前驱后继指针(维持顺序并支持取消节点时把链补上),以及一个能被唤醒者识别的标记,否则一个节点取消就会让后面的线程永远收不到通知。

  3. 三个线程按序循环的实现与线程池设计取舍:a→b→c→a 这类按需循环,标准解法是一个共享的「当前轮到谁」状态加条件变量:用 ReentrantLock 配三个 Condition,每个线程在轮到自己之前 await、执行完改状态并 signal 下一个;也可以用 Semaphore 用一个许可令牌在线程间传递,或用 CountDownLatch 做一次性顺序(循环场景不适合)。要注意虚假唤醒(await 必须放在 while 循环里判断条件)、异常路径下也要把令牌交出去否则整条链卡死、以及别用忙等+sleep 这种既费 CPU 又有延迟的做法。线程池为什么「先核心线程、再入队、队列满才扩到最大」:线程创建与销毁的成本远高于任务在队列里等待,先复用已有线程能控制并发度、避免频繁切换;如果先扩到最大再排队,CPU 会在高并发下被线程切换拖垮,而队列失去了削峰作用。反过来说,如果任务延迟敏感、不允许排队,就该用有界小队列 + 较大的 max,或者直接用 SynchronousQueue 把任务立刻交给线程或触发拒绝。手写线程池要覆盖的点:Worker 继承 AQS 或直接用锁、runWorker 的取任务循环、allowCoreThreadTimeOut、拒绝策略、以及 shutdown 时中断空闲线程并等已提交任务跑完。

面经 01 / 03 / 04 · MySQL

  1. 执行计划、索引与 B+ 树容量:EXPLAIN 要重点看 type(ALL 全表最差、index 全索引扫描、range/ref/eq_ref/const 依次变好)、key(实际用的索引,NULL 说明没走索引)、rows(预估扫描行数,用于判断索引选择是否合理)、filtered、Extra(Using filesort、Using temporary 是排序与临时表开销,Using index 表示覆盖索引)。explain 看起来没问题但仍然很慢,要从别处找原因:数据量本身很大(需要归档或分表)、并发高导致锁等待或磁盘 IO 打满、单次返回结果集过大(网络传输与序列化成为瓶颈)、SQL 写法让排序或聚合在临时表里做、索引统计信息过期导致优化器选错执行计划(analyze table)、以及缓冲池命中率低。三层的 B+ 树能存多少数据是可以现场推的:假设页 16K、主键 bigint 8 字节加 6 字节页指针,非叶子节点一个索引项约 14 字节,一页能放约 1170 个,两层就到了约 137 万条指向叶子;叶子节点每行假设 1KB,一页放约 16 行,所以三层大约能放两千万行量级——答题时要给出推导过程而不是背一个数,并说明这个估算对行宽和主键长度非常敏感。InnoDB 靠 redo log(崩溃恢复与持久性)、undo log(回滚与 MVCC 版本链)、锁与 MVCC(隔离性)以及约束(一致性)共同实现 ACID,另外要提到两阶段提交保证 redo 与 binlog 的逻辑一致。

面经 01 / 05 · Redis 与缓存架构

  1. Redis 的数据结构、分片与缓存一致性:string 底层是 SDS,好处是记录了长度所以取长度 O(1)(C 字符串要遍历)、二进制安全(能存任意字节)、预分配与惰性释放减少内存重分配次数,且不会因为 \0 截断。list 在元素少时用紧凑列表(ziplist / listpack),大了转 quicklist(链表 + 紧凑列表的组合),兼顾内存与操作效率。分片集群(Cluster)把整个键空间划分为 16384 个哈希槽,键通过 CRC16(key) mod 16384 决定归属槽,客户端拿到 MOVED 重定向或本地缓存槽映射表;扩容时新增节点会迁移部分槽,迁移中是「ASK 重定向 + 逐个 key 搬运」,此时可能有短暂的访问异常,所以扩容要挑低峰并配合客户端重试。这就是「底层是否用哈希实现」的落点:是哈希分片,但不是把 key 全量哈希到节点,而是哈希到槽、槽再分配给节点。缓存一致性:旁路缓存(Cache-Aside)在并发下确实会脏——先更新库再删缓存仍有窗口(删除失败或读请求在删除后回填旧值),把缓存当准会有脏读;工程做法是先更新数据库再删除缓存、加延迟双删或订阅 binlog 失效、给缓存设 TTL 兜底、关键业务用「读写都加锁」或索性不用缓存保正确性。Redis 取缓存变慢的常见原因是:慢命令(大 key 的 hgetall、keys、大 zset 范围查询)阻塞单线程、网络往返增加(跨机房、连接数暴涨)、内存不足触发了淘汰或 swap、持久化 fork 导致抖动、以及热 key 打满单个分片——排查先看 slowlog 与延迟监控。

  2. 数十万 QPS 的抽奖活动设计:这类题按「入口、库存、去重、异步化、兜底」五段答。入口先挡:客户端限流、验证码或答题挡脚本、网关层按用户与 IP 限流,把无效流量在第一层削掉。库存必须提前预热到 Redis,用 Lua 把「校验库存 + 扣减 + 记录已参与用户」合成一次原子操作,防止超发与重复中奖;奖品只有几百个而参与几十万,最坏情况是绝大多数请求都是来抢那几百份,所以要么用「先到先得 + 库存为 0 立即快速失败」,要么改成「抽奖资格 + 异步开奖」把瞬时写压力拆掉。去重按用户维度做(活动 ID + 用户 ID 的唯一约束,Redis 用 set 或 bitmap 判断是否已参与),并在数据库层用唯一索引做最终保证。异步化是把中奖记录、发券、通知这些非关键路径放到 MQ 里慢慢消费,主链路只做「判断 + 扣减」,失败靠重试与对账补偿。兜底要有:库存回补(下单超时或发奖失败时把库存还回去,且回补必须幂等)、对账任务扫「库存为负」「同一用户多次中奖」、以及降级预案(活动太热时关闭部分入口或改为预约制)。答题时主动提「Redis 单点与主从切换会丢扣减记录」这一风险,并给出「数据库条件更新兜底 + 对账修复」的对策,才算完整。

面经 05 / 06 / 07 · 系统设计与分布式

  1. 三层缓存的更新与多实例失效:本地缓存 + Redis + MySQL 的架构下,难点是本地缓存不受控——多实例各自的本地缓存在数据变更时都要失效。可行方案按代价排序:一是给本地缓存设很短的 TTL(秒级)并以 Redis 为准,接受秒级不一致;二是用消息队列广播失效消息,每个实例订阅并清掉自己的本地缓存,注意消息要带版本号或时间戳,避免乱序导致用旧值覆盖新值;三是用配置中心或 Redis 的发布订阅做通知通道,实现更轻但要处理断连重连期间的丢失(重连后强制清一次本地缓存);四是把版本号放在共享存储(Redis 里维护每个 key 的版本),本地缓存命中后异步校验版本,过期则回源。整体顺序是「先写库 → 删 Redis → 广播失效本地」,任何一步失败都要靠 TTL 与下一次读回源收敛;本地缓存只适合放「允许短时间不一致、读远多于写」的数据,强一致数据不要放本地。Kafka 的广播是另一回事:同一消费者组内每个分区只被一个消费者消费,要让多个消费者都拿到同一条消息,就要让它们属于不同的消费者组(各自独立的 group.id),或者用不同的 topic/分区策略;底层原理是分区是并行与顺序的基本单位、位点(offset)按「消费者组 + 分区」维护,所以广播的本质是「多组各自维护一份位点」。

  2. 零拷贝与订票场景的分布式事务:零拷贝的核心是让数据不经过用户态缓冲区反复拷贝,常见实现是 sendfile(文件 → socket 直接在内核里流转)、mmap + write、splice,Kafka 与 Netty 都用这套把磁盘到网络的数据路径缩短;系统调用层面记 sendfile、splice、mmap、sendmsg 即可,答题时补一句「零拷贝减少的是 CPU 拷贝与上下文切换,不是完全没有拷贝(DMA 拷贝仍在)」。订票这类「先查票、再支付、半小时后才出结果」的场景,本质是长事务加多个外部依赖,不能用数据库事务硬扛。推荐的组合是:本地消息表或事务消息保证「订单状态变更」与「待执行动作」原子落地,然后用状态机驱动后续步骤(待支付 → 支付中 → 出票中 → 已出票 / 已退款),每一步都幂等可重试,超时未回调就走主动查询接口对账,最终由定时对账修掉不一致;支付与出票这种依赖第三方的步骤要设最大重试与人工兜底队列。消息可能传递失败,所以投递侧要有确认与重发(至少一次),消费侧靠唯一键幂等;选择 MQ 时按「是否需要事务消息、延迟消息、严格顺序、堆积能力」来定,这类业务一般需要事务消息与延迟消息,RocketMQ 更贴合,Kafka 更适合日志与流处理。