常见八股文汇总
Mo
常见八股文汇总
并发
什么是乐观锁?什么是悲观锁?
- 乐观锁:假设并发操作时不会冲突,只在数据更新时加锁;
- 适用于读多写少场景,提高了并发性能,但存在版本号回滚问题(ABA)
- 悲观锁:假设并发操作时会冲突,先加锁再操作(如 Synchronized、ReentrantLock)。
- 适用于读多写少、安全性要求更高的场景,数据一致性高
CAS 是怎么实现的?
- “比较并交换”,通过 CPU 原子指令实现:先比较内存值与预期值,一致则更新为新值,否则失败(需重试)。
Synchronized 和 ReentrantLock 有什么区别?
| 特性 |
Synchronized |
ReentrantLock |
| 实现 |
JVM 关键字 |
Java 类 |
| 加解锁 |
自动 |
手动 lock/unlock |
| 功能 |
基础加锁 |
支持中断、公平锁、条件变量 |
[!question] 为什么使用synchronized而不是ReentrantLock
- 性能考量:Java 8优化后的synchronized在单bucket竞争时性能更好
- 内存占用:synchronized的JVM内置锁更节省内存
- JIT优化:锁消除/锁膨胀等优化更成熟
原子类是如何实现的?
- 基于 CAS 实现,通过 Unsafe 类调用 CPU 原子指令,保证自增、赋值等操作的原子性,无需加锁。
volatile 关键字有什么作用?
- 保证可见性(线程修改后立即同步到主存);
- 禁止指令重排序;但不保证原子性。
什么是指令重排序?
- CPU/编译器为优化性能,调整指令执行顺序(如“先加载后赋值”改成“先赋值后加载”),可能导致多线程逻辑错乱。
什么是 happens-before 原则?
- JMM 定义的“先行发生”规则(如程序顺序、volatile 写读、锁释放-获取),保证前一个操作结果对后一个操作可见。
Synchronized 的锁升级流程是什么?
- 无锁 → 偏向锁(记录线程 ID,减少开销) → 轻量级锁(CAS 加锁,少量竞争) → 重量级锁(操作系统互斥量,激烈竞争)。
Synchronized 是不是可重入锁?
- 是,同一线程可多次获取同一把锁(如递归调用时不会死锁)。
可重入锁是为了保证什么?
- 避免“线程自己锁自己”导致的死锁,同时支持递归调用、多层加锁场景。
AQS 队列是怎么实现的?
- AQS(抽象队列同步器)用双向链表维护等待线程队列,通过
state 变量控制锁状态,子类需实现 tryAcquire / tryRelease 等核心方法。
AQS 是怎么实现公平锁的?
- 判断等待队列是否有“前驱节点”:有则排队,不允许插队;无则尝试获取锁,保证“先到先得”。
线程池的核心参数是什么?
- 核心线程数、最大线程数、空闲线程存活时间、工作队列、拒绝策略、线程工厂。
线程池提交任务的流程是怎样的?
- 核心线程未满 → 创建核心线程执行;
- 核心线程满 → 放入工作队列;
- 队列满 → 创建非核心线程;
- 非核心线程满 → 触发拒绝策略。
线程池的核心参数怎么计算?
- CPU 密集型(如计算):核心线程数 = CPU 核心数 ±1;
- IO 密集型(如数据库操作):核心线程数 = CPU 核心数 × 2。
接口和抽象类的区别是什么?
| 维度 |
接口 |
抽象类 |
| 方法 |
全抽象(Java 8 后有默认方法) |
可含普通方法 |
| 继承/实现 |
多实现 |
单继承 |
| 设计意图 |
定义规范 |
定义模板 |
什么是单例模式?
- 保证一个类只有一个实例,并提供全局唯一的访问入口(如 Spring 的 Bean 默认单例)。
能写一个双重锁检查的单例吗?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| public class Singleton { private static volatile Singleton instance; private Singleton() {} public static Singleton getInstance() { if (instance == null) { synchronized (Singleton.class) { if (instance == null) { instance = new Singleton(); } } } return instance; } }
|
MySQL 的事务隔离级别有哪些?
- 4 级:读未提交(RU)、读已提交(RC)、可重复读(RR,默认)、串行化(Serializable)。
什么是 ACID?
- 原子性(Atomicity):要么全执行,要么全回滚;
- 一致性(Consistency):事务前后数据合法;
- 隔离性(Isolation):事务间互不干扰;
- 持久性(Durability):提交后数据永久保存。
MVCC 下可重复读是怎么实现的?
- 通过undo log(历史版本日志) 和read view(读视图)
- 实现:每个事务有独立 read view,读取 undo log 中的历史版本,保证多次读取一致。
MVCC 下可重复读还有幻读的问题吗?
- 有。InnoDB 通过间隙锁(锁定索引间隙,防止插入新数据)解决幻读。
什么是间隙锁?
- 锁定索引之间的“间隙”(如 id=1 和 id=3 之间),防止插入数据,主要用于解决幻读。
什么是零件锁?(推测为“临键锁”)
- 临键锁 = 行锁 + 间隙锁,锁定当前记录及前后间隙(InnoDB 默认的行锁算法)。
什么是索引的回表查询?
- 用非聚簇索引(如普通索引)查到主键后,需再去聚簇索引(主键索引)查询完整数据,两次查询就是回表。
怎么避免回表查询?
- 用覆盖索引:查询的字段全部包含在索引中(如
select id,name from user where name='a',若 name 是索引则无需回表)。
MySQL 有哪些常见的索引?
- 主键索引、普通索引、唯一索引、前缀索引、联合索引、全文索引。
索引在什么情况下会失效?
- 索引列用函数/表达式(如
where abs(id)=1);
- 模糊查询以
% 开头(如 like '%a');
- 联合索引不满足最左前缀;
- 类型转换(如字符串用数字查)。
有用过 explain 这个关键字吗?
- 用过,用来分析 SQL 执行计划,查看是否走索引、扫描行数、连接方式等,优化 SQL。
InnoDB 下 MySQL 的索引数据结构是什么?
- B+树:聚簇索引(主键)的叶子节点存完整数据,二级索引叶子节点存主键。
MySQL为什么选 B+树不选别的?
- 高度低,即查询层数更少(3-4 层即可存千万数据,查询快);
- 叶子节点用链表连接,支持范围查询;
- 非叶子节点只存索引键,节省内存。
有了解过 MySQL 的三大日志吗?
- redo log:保证持久性,记录数据修改,崩溃可恢复;
- undo log:事务回滚、MVCC 依赖,记录数据旧版本;
- binlog:归档日志,用于主从同步、数据恢复。
Redis 有哪些数据类型?
- 基础:String、Hash、List、Set、Zset;
- 高级:Bitmap、HyperLogLog、Geospatial。
Redis 各数据类型的主要应用场景?
- String:缓存、计数器、Session;
- Hash:存对象(如用户信息);
- List:队列、消息通知;
- Set:去重、好友交集;
- Zset:排行榜、延迟队列。
缓存穿透、缓存击穿和缓存雪崩的问题?
- 穿透:查不存在的数据(如 id=-1),解决:布隆过滤器、缓存空值;
- 击穿:热点 key 突然失效,解决:互斥锁、热点 key 永不过期;
- 雪崩:大量 key 同时失效,解决:过期时间加随机值、集群部署。
Redis 和数据库的一致性问题怎么解决?
- 常用“延迟双删”:1. 先删缓存;2. 更新数据库;3. 延迟几百毫秒再删一次缓存(解决并发脏读)。
Redis 为什么快?
- 基于内存操作;
- 单线程(避免线程切换开销)
- IO 多路复用模型;
- 数据结构简单(如跳表、哈希表)。
Redis 的 IO 多路复用模型是什么?
- 用 select/poll/epoll 监听多个 Socket 连接,单线程处理多个请求,减少 IO 阻塞,高效利用 CPU。
能用 Redis 实现一套登录机制吗?
- 用户登录成功,生成唯一token;
- 以
token 为 key,用户信息 为 value 存 Redis,设置过期时间(如 2 小时);
- 后续请求携带 token,校验 Redis 中是否存在即可。
能用 Redis 做防抖和节流吗?
- 防抖:用户频繁操作时(如输入),用
setex key 10 "",重复操作则刷新过期时间,10 秒内只执行最后一次;
- 节流:用
incr key + expire key 60,限制 1 分钟内最多执行N次。
了解 Redis 的主从同步吗?
主库写操作记录 binlog;从库连接主库,先全量同步主库数据,后增量同步主库的 binlog,保持数据一致(主从分离,主写从读)。
了解 Redis 的持久化机制吗?
- RDB:快照,全量备份(快但可能丢数据);
- AOF:日志,增量记录命令(可靠但文件大);通常混合使用(RDB+AOF)。
Redis 中的大 key 和热 key 该怎么优化?
- 大 key:拆分(如大 Hash 拆成多个小 Hash)、分段存储;
- 热 key:多副本(如 key 1、key 2 都存同一数据)、本地缓存、拆分 key 分散压力。
做过 JVM 的调优吗?
- 做过,比如:调整堆大小(Xms/Xmx)、设置 G 1 垃圾收集器、优化新生代/老年代比例、排查内存泄露(如未关闭连接)。
JVM 有哪些垃圾回收算法?
- 标记-清除:效率低、有碎片;
- 标记-复制:适合新生代(无碎片,浪费一半空间);
- 标记-整理:适合老年代(无碎片,效率低);
- 分代收集:结合以上三种,新生代用复制,老年代用整理/清除。
JVM 的内存空间是怎么分配的?
- 线程私有:虚拟机栈、本地方法栈、程序计数器;
- 线程共享:堆(新生代 Eden/Survivor、老年代)、方法区(元空间)。
什么是逃逸分析?
- JVM 分析对象是否“逃出”方法/线程(如被外部引用),若未逃逸,可做栈上分配(减少 GC)、标量替换(拆分对象)。
如何避免 OutOfMemory 错误?
- 合理设置堆大小;2. 排查内存泄露(如静态集合持有对象、未关流);3. 优化对象创建(如用池化技术);4. 用 Arthas 等工具监控内存。
项目中遇到过内存泄露吗?
- 遇到过,比如:静态 List 长期持有用户数据、未关闭的数据库连接、线程池核心线程持有大对象。
怎么解决内存泄露?
- 用 Profiler 工具(如 VisualVM)定位泄露点;2. 清理静态引用、及时关闭资源;3. 调整线程池参数,避免核心线程长期持有对象。
Spring Boot 涉及了哪些设计模式?
- 单例(Bean 默认)、工厂(BeanFactory)、代理(AOP)、观察者(事件监听)、装饰器(BeanWrapper)。
什么是 IOC?
- 控制反转,将对象的创建、依赖管理交给Spring容器,不用手动
new,降低类之间的耦合。
Spring Boot 中 A 依赖 B、B 依赖 A,怎么解决?
- 单例 Bean 通过三级缓存解决:1. singletonFactories(工厂缓存)→ 2. earlySingletonObjects(早期对象缓存)→ 3. singletonObjects(成品缓存),提前暴露未初始化的 Bean 引用。
AOP 是怎么实现的?
- 通过动态代理:1. 对接口用 JDK 动态代理;2. 对类用 CGLIB 代理(生成子类);在代理对象中织入日志、事务等增强逻辑。
除了 JDK 的动态代理,还了解其他代理模式吗?
- 静态代理:手动写代理类,编译时生成;
- CGLIB 代理:基于 ASM 字节码框架,生成目标类的子类实现代理。
Spring 的 AOP 是运行时代理还是编译时代理?
- 运行时代理,通过动态代理在程序运行时生成代理对象,无需修改目标类源码。
事务注解什么时候会失效?
- 非 public 方法(事务注解只对 public 生效);2. 自调用(未通过 Spring 代理,如 this.方法());3. 异常被捕获(未抛出);4. 未配置事务管理器。