Java 后端面试高频题整理(字节高频面试题01)
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 动态代理
执行流程
当调用目标方法时:
- 实际调用的是代理对象
- 进入拦截器链
- 执行 before / around / after 等增强逻辑
- 最终调用目标方法
总结
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 为例:
- 创建 A,实例化后放入三级缓存
- A 依赖 B,开始创建 B
- B 依赖 A,从三级缓存获取 A 的早期引用
- B 初始化完成
- 回到 A,完成初始化
- 最终放入一级缓存
为什么需要三级缓存
为了支持 AOP 代理对象的提前暴露。
限制
仅支持:
- 单例 Bean
- setter / 字段注入
不支持构造器循环依赖。
6. HTTP 请求全过程
问题
HTTP 请求从浏览器到服务器的过程?
标准回答
- 浏览器解析 URL
- 检查浏览器缓存
- DNS 解析获取 IP
- TCP 三次握手
- HTTPS 场景下进行 TLS 握手
- 发送 HTTP 请求
- 服务器处理请求
- 返回 HTTP 响应
- 浏览器解析并渲染页面
- TCP 四次挥手关闭连接
7. HTTP 与 HTTPS 的区别
问题
HTTP 和 HTTPS 的区别?
标准回答
HTTP
- 明文传输
- 不安全
- 默认端口 80
HTTPS
HTTPS = HTTP + TLS
特点:
- 数据加密
- 防篡改
- 身份认证
- 默认端口 443
HTTPS 流程
- TCP 三次握手
- TLS 握手
- 验证证书
- 协商对称密钥
- 加密通信
总结
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 主从复制分为:
- 全量复制
- 增量复制
全量复制
流程:
- 从节点发送 PSYNC
- 主节点返回 FULLRESYNC
- 主节点生成 RDB
- 从节点加载 RDB
- 主节点发送缓冲区命令
- 从节点同步完成
增量复制
依赖:
- replication id
- offset
- repl backlog buffer
流程:
- 从节点发送 offset
- 主节点从 backlog 中补发缺失数据
- 从节点执行命令完成同步
12. HashMap 底层原理
问题
HashMap 底层原理?发生哈希冲突如何处理?
标准回答
JDK1.8 HashMap 底层结构:
- 数组
- 链表
- 红黑树
put 流程
- 计算 hash
- 定位桶位置
- 桶为空直接插入
- 冲突时遍历链表或红黑树
哈希冲突
- equals 相同:覆盖 value
- 不同:链表追加
树化条件
满足:
- 链表长度 >= 8
- 数组长度 >= 64
才会树化。
为什么红黑树
- 链表:O(n)
- 红黑树:O(logn)
13. 线程池
问题
线程池了解吗?好处是什么?
标准回答
线程池本质是提前创建并统一管理线程的容器。
七个核心参数
- corePoolSize:核心线程数
- maximumPoolSize:最大线程数
- workQueue:任务队列
- keepAliveTime:线程存活时间
- unit:时间单位
- threadFactory:线程工厂
- handler:拒绝策略
工作流程
- 核心线程未满:直接执行
- 核心线程满:进入队列
- 队列满:创建非核心线程
- 达到最大线程数:执行拒绝策略
线程池好处
- 减少线程创建销毁开销
- 提高响应速度
- 控制并发数量
- 防止系统资源耗尽
- 统一管理线程
更多推荐
所有评论(0)