Java 后端面试高频题整理

1. MVCC 工作原理

问题

MVCC 工作原理?

标准回答

MVCC(多版本并发控制)的核心目的是:在多事务并发场景下,实现读不加锁,从而提高并发性能。

它主要依赖两个核心组件:ReadView(读视图)和 undo log(版本链)。

ReadView 的作用

ReadView 用来判断当前事务能看到哪些数据版本,它包含几个关键字段:

  • creator_trx_id:当前事务ID
  • m_ids:当前活跃的未提交事务ID列表
  • min_trx_id:活跃事务中最小的ID
  • max_trx_id:下一个将要分配的事务ID

在不同隔离级别下:

  • 读已提交(RC):每次 SELECT 都生成新的 ReadView
  • 可重复读(RR):第一次 SELECT 时生成,后续复用
数据的多版本(undo log)

每一行数据都会有:

  • trx_id:最后修改它的事务ID
  • roll_pointer:指向 undo log

通过 undo log,可以形成一条版本链,存储历史数据。

可见性判断规则

当执行 SELECT 时,会用当前 ReadView 判断某个版本是否可见:

  • trx_id < min_trx_id:可见
  • trx_id >= max_trx_id:不可见
  • 在中间:
    • 在 m_ids 中:不可见
    • 不在 m_ids 中:可见

如果当前版本不可见,就通过 undo log 沿着版本链向前找,直到找到一个可见版本。

总结

MVCC 通过 ReadView + undo log 版本链,实现了“读历史版本”,从而做到读不加锁,提高并发性能。


2. 乐观锁和悲观锁的区别

问题

乐观锁和悲观锁的区别?

标准回答

乐观锁和悲观锁的核心区别在于对并发冲突的假设不同,以及控制并发的方式不同。

对冲突的假设不同
  • 乐观锁:假设冲突很少发生,因此更新时才校验
  • 悲观锁:假设冲突一定会发生,因此操作前先加锁
实现方式

乐观锁通常通过:

  • CAS
  • version 版本号

实现。

例如:

update table 
set value = xxx, version = version + 1
where id = 1 and version = old_version;

悲观锁通常通过:

  • synchronized
  • ReentrantLock
  • select … for update

实现。

性能区别
  • 乐观锁:不阻塞,性能高,但冲突多时会频繁重试
  • 悲观锁:会阻塞线程,但逻辑更稳定
适用场景
  • 乐观锁:读多写少
  • 悲观锁:写多、强一致场景
总结

乐观锁通过“检测冲突”保证并发安全,悲观锁通过“避免冲突”保证并发安全。


3. AOP 底层原理

问题

AOP 底层原理?

标准回答

AOP(面向切面编程)的核心思想是:把日志、事务、权限校验等横切逻辑从业务代码中抽离出来,实现统一管理。

AOP 核心概念
  • Aspect:切面
  • Advice:通知
  • JoinPoint:连接点
  • Pointcut:切点
底层实现

Spring AOP 底层基于动态代理实现:

  • 有接口:JDK 动态代理
  • 无接口:CGLIB 动态代理
执行流程

当调用目标方法时:

  1. 实际调用的是代理对象
  2. 进入拦截器链
  3. 执行 before / around / after 等增强逻辑
  4. 最终调用目标方法
总结

Spring AOP 本质是通过动态代理,在方法调用前后构建拦截器链,实现对方法执行过程的增强。


4. Redis 大 Key 与热 Key

问题

如何解决 Redis 大 Key 和热 Key 问题?

标准回答

大 Key

大 Key 指单个 key 的 value 过大,会导致:

  • 网络传输慢
  • Redis 阻塞
  • 主从同步慢
  • 持久化开销大

解决方案:

  • 按业务维度拆分 key
  • 控制 value 大小
  • 使用 UNLINK 异步删除
  • 使用 bigkeys / SCAN 排查
热 Key

热 Key 指某个 key 被高频访问。

解决方案:

  • 本地缓存(Caffeine)
  • 热点 key 多副本
  • 永不过期 + 异步刷新
  • 互斥锁重建缓存
  • 限流和降级
总结

大 Key 的核心是拆小,热 Key 的核心是分散访问压力并避免缓存击穿。


5. Spring 如何解决循环依赖

问题

Spring 如何解决循环依赖?

标准回答

Spring 通过三级缓存 + 提前暴露对象解决循环依赖。

三级缓存
  • 一级缓存 singletonObjects:完全初始化好的 Bean
  • 二级缓存 earlySingletonObjects:提前暴露的半成品 Bean
  • 三级缓存 singletonFactories:对象工厂
解决流程

以 A 依赖 B,B 依赖 A 为例:

  1. 创建 A,实例化后放入三级缓存
  2. A 依赖 B,开始创建 B
  3. B 依赖 A,从三级缓存获取 A 的早期引用
  4. B 初始化完成
  5. 回到 A,完成初始化
  6. 最终放入一级缓存
为什么需要三级缓存

为了支持 AOP 代理对象的提前暴露。

限制

仅支持:

  • 单例 Bean
  • setter / 字段注入

不支持构造器循环依赖。


6. HTTP 请求全过程

问题

HTTP 请求从浏览器到服务器的过程?

标准回答

  1. 浏览器解析 URL
  2. 检查浏览器缓存
  3. DNS 解析获取 IP
  4. TCP 三次握手
  5. HTTPS 场景下进行 TLS 握手
  6. 发送 HTTP 请求
  7. 服务器处理请求
  8. 返回 HTTP 响应
  9. 浏览器解析并渲染页面
  10. TCP 四次挥手关闭连接

7. HTTP 与 HTTPS 的区别

问题

HTTP 和 HTTPS 的区别?

标准回答

HTTP
  • 明文传输
  • 不安全
  • 默认端口 80
HTTPS

HTTPS = HTTP + TLS

特点:

  • 数据加密
  • 防篡改
  • 身份认证
  • 默认端口 443
HTTPS 流程
  1. TCP 三次握手
  2. TLS 握手
  3. 验证证书
  4. 协商对称密钥
  5. 加密通信
总结

HTTPS 在 HTTP 基础上增加 TLS,通过加密和证书机制保证数据安全。


8. 公钥与私钥

问题

什么是公钥和私钥?

标准回答

非对称加密中会生成一对密钥:

  • 公钥:可以公开
  • 私钥:必须保密
两种使用方式
1. 公钥加密,私钥解密

用于保证数据机密性。

2. 私钥签名,公钥验证

用于保证身份认证和数据完整性。

HTTPS 中的作用

客户端使用服务器公钥加密对称密钥,服务器使用私钥解密。


9. 缓存穿透与布隆过滤器

问题

缓存穿透什么场景适合缓存空值?什么时候用布隆过滤器?布隆过滤器原理是什么?

标准回答

缓存空值

适合:

  • 少量不存在数据
  • 请求 key 较稳定

优点:

  • 实现简单

缺点:

  • 容易缓存膨胀

布隆过滤器

适合:

  • 高并发
  • 大量随机 key
  • 恶意攻击场景
原理

布隆过滤器 = 位数组 + 多个 hash 函数。

插入:

  • 多次 hash
  • 对应位置置 1

查询:

  • 有 0:一定不存在
  • 全是 1:可能存在
问题
  • 误判
  • 不支持删除
  • 数据一致性问题
解决方案
  • 增大位数组
  • 使用计数布隆过滤器
  • 定期重建

10. Redis 常用数据结构

问题

Redis 常用数据结构?SDS 与普通字符串区别?ZSet 底层原理?

标准回答

Redis 常用数据结构
  • String
  • Hash
  • List
  • Set
  • ZSet

扩展结构:

  • Stream
  • Bitmap
  • HyperLogLog
  • Geo

SDS

Redis String 底层使用 SDS(Simple Dynamic String)。

特点:

  • O(1) 获取长度
  • 防止缓冲区溢出
  • 预分配减少扩容
  • 支持二进制安全

ZSet

适用场景:

  • 排行榜
  • 延时队列
  • 优先级队列

底层结构:

  • 跳表(skiplist)
  • 字典(dict)

跳表本质是多层有序链表,通过多级索引实现 O(logn) 查询。


11. Redis 主从复制

问题

Redis 主从复制?

标准回答

Redis 主从复制分为:

  • 全量复制
  • 增量复制

全量复制

流程:

  1. 从节点发送 PSYNC
  2. 主节点返回 FULLRESYNC
  3. 主节点生成 RDB
  4. 从节点加载 RDB
  5. 主节点发送缓冲区命令
  6. 从节点同步完成

增量复制

依赖:

  • replication id
  • offset
  • repl backlog buffer

流程:

  1. 从节点发送 offset
  2. 主节点从 backlog 中补发缺失数据
  3. 从节点执行命令完成同步

12. HashMap 底层原理

问题

HashMap 底层原理?发生哈希冲突如何处理?

标准回答

JDK1.8 HashMap 底层结构:

  • 数组
  • 链表
  • 红黑树

put 流程
  1. 计算 hash
  2. 定位桶位置
  3. 桶为空直接插入
  4. 冲突时遍历链表或红黑树

哈希冲突
  • equals 相同:覆盖 value
  • 不同:链表追加

树化条件

满足:

  • 链表长度 >= 8
  • 数组长度 >= 64

才会树化。


为什么红黑树
  • 链表:O(n)
  • 红黑树:O(logn)

13. 线程池

问题

线程池了解吗?好处是什么?

标准回答

线程池本质是提前创建并统一管理线程的容器。


七个核心参数
  1. corePoolSize:核心线程数
  2. maximumPoolSize:最大线程数
  3. workQueue:任务队列
  4. keepAliveTime:线程存活时间
  5. unit:时间单位
  6. threadFactory:线程工厂
  7. handler:拒绝策略

工作流程
  1. 核心线程未满:直接执行
  2. 核心线程满:进入队列
  3. 队列满:创建非核心线程
  4. 达到最大线程数:执行拒绝策略

线程池好处
  • 减少线程创建销毁开销
  • 提高响应速度
  • 控制并发数量
  • 防止系统资源耗尽
  • 统一管理线程

更多推荐