常见八股文汇总

常见八股文汇总

并发

什么是乐观锁?什么是悲观锁?

  • 乐观锁:假设并发操作时不会冲突,只在数据更新时加锁;
    • 适用于读多写少场景,提高了并发性能,但存在版本号回滚问题(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 关键字有什么作用?

  1. 保证可见性(线程修改后立即同步到主存);
  2. 禁止指令重排序;但不保证原子性。

什么是指令重排序?

  • CPU/编译器为优化性能,调整指令执行顺序(如“先加载后赋值”改成“先赋值后加载”),可能导致多线程逻辑错乱。

什么是 happens-before 原则?

  • JMM 定义的“先行发生”规则(如程序顺序、volatile 写读、锁释放-获取),保证前一个操作结果对后一个操作可见。

Synchronized 的锁升级流程是什么?

  • 无锁 → 偏向锁(记录线程 ID,减少开销) → 轻量级锁(CAS 加锁,少量竞争) → 重量级锁(操作系统互斥量,激烈竞争)。

Synchronized 是不是可重入锁?

  • 是,同一线程可多次获取同一把锁(如递归调用时不会死锁)。

可重入锁是为了保证什么?

  • 避免“线程自己锁自己”导致的死锁,同时支持递归调用、多层加锁场景。

AQS 队列是怎么实现的?

  • AQS(抽象队列同步器)用双向链表维护等待线程队列,通过 state 变量控制锁状态,子类需实现 tryAcquire / tryRelease 等核心方法。

AQS 是怎么实现公平锁的?

  • 判断等待队列是否有“前驱节点”:有则排队,不允许插队;无则尝试获取锁,保证“先到先得”。

线程池的核心参数是什么?

  • 核心线程数、最大线程数、空闲线程存活时间、工作队列、拒绝策略、线程工厂。

线程池提交任务的流程是怎样的?

  1. 核心线程未满 → 创建核心线程执行;
  2. 核心线程满 → 放入工作队列;
  3. 队列满 → 创建非核心线程;
  4. 非核心线程满 → 触发拒绝策略。

线程池的核心参数怎么计算?

  • 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 {
// volatile防止指令重排序
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 有哪些常见的索引?

  • 主键索引、普通索引、唯一索引、前缀索引、联合索引、全文索引。

索引在什么情况下会失效?

  1. 索引列用函数/表达式(如 where abs(id)=1);
  2. 模糊查询以 % 开头(如 like '%a');
  3. 联合索引不满足最左前缀;
  4. 类型转换(如字符串用数字查)。

有用过 explain 这个关键字吗?

  • 用过,用来分析 SQL 执行计划,查看是否走索引、扫描行数、连接方式等,优化 SQL。

InnoDB 下 MySQL 的索引数据结构是什么?

  • B+树:聚簇索引(主键)的叶子节点存完整数据,二级索引叶子节点存主键。

MySQL为什么选 B+树不选别的?

  1. 高度低,即查询层数更少(3-4 层即可存千万数据,查询快);
  2. 叶子节点用链表连接,支持范围查询;
  3. 非叶子节点只存索引键,节省内存。

有了解过 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 为什么快?

  1. 基于内存操作;
  2. 单线程(避免线程切换开销)
  3. IO 多路复用模型;
  4. 数据结构简单(如跳表、哈希表)。

Redis 的 IO 多路复用模型是什么?

  • 用 select/poll/epoll 监听多个 Socket 连接,单线程处理多个请求,减少 IO 阻塞,高效利用 CPU。

能用 Redis 实现一套登录机制吗?

  1. 用户登录成功,生成唯一token;
  2. token 为 key,用户信息 为 value 存 Redis,设置过期时间(如 2 小时);
  3. 后续请求携带 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 错误?

  1. 合理设置堆大小;2. 排查内存泄露(如静态集合持有对象、未关流);3. 优化对象创建(如用池化技术);4. 用 Arthas 等工具监控内存。

项目中遇到过内存泄露吗?

  • 遇到过,比如:静态 List 长期持有用户数据、未关闭的数据库连接、线程池核心线程持有大对象。

怎么解决内存泄露?

  1. 用 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 是运行时代理还是编译时代理?

  • 运行时代理,通过动态代理在程序运行时生成代理对象,无需修改目标类源码。

事务注解什么时候会失效?

  1. 非 public 方法(事务注解只对 public 生效);2. 自调用(未通过 Spring 代理,如 this.方法());3. 异常被捕获(未抛出);4. 未配置事务管理器。