Java 后端面试题(一)

Java 后端核心面试题


一、如何保证方法线程安全?

问题: 写了一个方法,如何保证该方法是线程安全的?

答案:

方式说明
无共享变量方法内部仅使用局部变量,不共享成员变量,天然线程安全
使用锁机制synchronized(隐式锁)、Lock(显式锁),保证同一时刻只有一个线程执行临界区代码
使用线程安全类ConcurrentHashMapCopyOnWriteArrayListAtomic 原子类等
变量不可变使用 final 修饰共享变量,禁止修改
线程私有通过 ThreadLocal 让每个线程拥有独立变量副本,避免共享

二、synchronized 锁的级别及升级机制

问题: synchronized 是重量级锁还是轻量级锁?是一开始就是轻量级,还是一直不变?

答案:

synchronized 并非固定锁级别,JDK1.6 后做了锁优化,存在锁升级机制:

text
复制代码
偏向锁 → 轻量级锁 → 重量级锁(只能升级,不能降级)
锁级别触发条件实现方式特点
偏向锁无竞争时默认开启CAS 记录线程 ID几乎无开销
轻量级锁出现轻微竞争(交替执行)自旋实现无内核态切换
重量级锁竞争激烈、自旋失败依赖操作系统内核阻塞线程,开销大

总结: 传统 synchronized 是纯重量级锁,现在是自适应升级的混合锁。


三、@Transactional 异常未回滚问题分析

问题: 方法加 @Transactional,抛出异常但数据库未回滚,如何排查分析?

答案: 常见原因及排查方向:

序号原因说明
1异常类型不对默认仅捕获运行时异常(RuntimeException)和 Error,普通受检异常 Exception 不会回滚,需手动指定 rollbackFor = Exception.class
2异常被内部捕获代码中 try-catch 吃掉异常,事务切面感知不到异常,不会触发回滚
3事务失效(同类内部调用)同类方法内部调用加事务的方法,AOP 无法增强,事务失效
4方法权限问题事务注解加在 private / protected 方法上,Spring AOP 无法拦截
5数据库引擎不支持事务表引擎为 MyISAM,改为 InnoDB
6传播属性配置错误传播行为设置为 NOT_SUPPORTED 等,强制非事务运行
7多数据源 / 事务管理器配置异常注解指定的事务管理器与数据源不匹配

四、同类调用、事务嵌套导致事务失效原因

问题: 调用同类方法为什么事务失效?事务嵌套为何也会失效?

4.1 同类内部调用失效

Spring 事务基于动态 AOP 代理实现,只有外部通过代理对象调用方法,才会被切面拦截、开启事务。同类中 this.方法() 是直接调用原对象,不走代理,事务注解无效。

解决方案:

  • 自身注入自己
  • 使用 AopContext 获取代理对象
  • 拆分方法到不同类

4.2 事务嵌套失效

主要由事务传播机制导致:

场景行为
外层无事务、内层 REQUIRED内层独立事务,互不影响
外层有事务、内层 REQUIRED并入外层事务,内层异常会导致整体回滚
内层 REQUIRES_NEW新建独立事务,但部分场景因连接、代理问题出现异常
内层吞掉异常外层无法感知,造成回滚异常

五、事务传播机制、四大特性、原子性理解

问题: 事务传播机制有几种?事务特性有哪些?原子性如何理解?

5.1 事务传播机制(共 7 种)

传播行为说明
REQUIRED支持当前事务,不存在则新建(默认)
SUPPORTS支持当前事务,不存在则以非事务运行
MANDATORY必须在事务中运行,不存在则抛异常
REQUIRES_NEW新建事务,挂起当前事务
NOT_SUPPORTED以非事务运行,挂起当前事务
NEVER以非事务运行,存在事务则抛异常
NESTED嵌套事务,外层回滚则内层也回滚,内层回滚不影响外层

5.2 事务四大特性(ACID)

特性英文说明
原子性Atomicity事务内所有操作要么全部成功,要么全部失败回滚
一致性Consistency事务执行前后,数据必须保持一致状态
隔离性Isolation并发事务之间互不干扰
持久性Durability事务提交后,数据永久保存

5.3 原子性理解

一个事务内的所有数据库操作要么全部成功提交,要么全部失败回滚,不可部分执行。比如下单扣库存、生成订单,两个操作必须同时生效或同时撤销。


六、Spring AOP 执行阶段 & 循环依赖解决方案

问题: Spring AOP 发生在 Bean 生命周期哪个阶段?Spring 循环依赖如何解决?

6.1 AOP 执行阶段

AOP 动态代理在 Bean 初始化完成后、放入单例池之前 生成代理对象。

text
复制代码
Bean 核心流程: 实例化 → 属性填充 → 初始化(@PostConstruct / init-method)→ AOP 代理 → 存入单例池

6.2 循环依赖解决

Spring 仅能解决单例 Bean 基于 setter 注入的循环依赖,依靠三级缓存:

缓存级别存储内容
一级缓存完整可用的单例 Bean
二级缓存已实例化、未完成属性填充的半成品 Bean
三级缓存Bean 的工厂对象(提前暴露实例)

原理: 实例化后提前暴露半成品,打破循环引用;构造器注入、多例 Bean 无法解决循环依赖。


七、项目中线程池使用、作用与选型原因

问题: 项目中如何使用线程池?为什么使用线程池?解决了什么问题?

7.1 使用方式

项目一般使用 ThreadPoolExecutor 手动创建线程池(禁止 Executors 默认工厂类,避免 OOM),根据业务设置核心线程、最大线程、队列、拒绝策略;用于异步处理、批量任务、文件导出、消息消费等场景。

7.2 使用原因 & 解决的问题

优势说明
降低性能开销避免频繁创建 / 销毁线程
统一管理线程控制并发数量,防止无限创建线程导致 OOM
提升吞吐量复用线程、任务排队
提升稳定性提供拒绝策略、监控、超时机制

八、大数据量导出 + 实时进度展示实现

问题: 大数据导出场景,如何实现展示导出进度(1%、5%…)?

答案: 整体采用异步导出 + 进度存储 + 前端轮询方案:

text
复制代码
1. 前端发起导出请求 → 后端开启异步线程执行导出任务 → 立即返回任务 ID 2. 每处理一批数据 → 将当前进度、任务状态存入 Redis / 内存 / 数据库(key = 任务 ID) 3. 前端根据任务 ID 轮询接口 → 查询 Redis 中的进度并展示 4. 导出完成 → 将文件地址写入缓存,前端拉取文件下载;异常则标记失败状态

优化: 数据分批查询、分批写入文件,避免一次性加载全量数据内存溢出。


九、死锁产生条件、排查、规避方案

问题: 什么情况会发生死锁?如何处理死锁?开发中如何避免?

9.1 死锁四大必要条件(同时满足才会死锁)

条件说明
互斥条件资源同一时刻只能被一个线程占用
请求与保持条件持有资源的同时请求其他资源
不可剥夺条件已获得的资源不能被强行释放
循环等待条件多个线程形成循环等待资源的关系

9.2 线上处理死锁

场景排查方式
MySQL 死锁SHOW ENGINE INNODB STATUS 定位死锁 SQL
Java 线程死锁使用 jstackArthas 排查阻塞线程与锁持有关系
临时恢复重启服务、终止卡死请求

9.3 代码规避死锁(破坏四大条件)

  • 统一锁顺序: 所有线程按固定顺序获取多把锁
  • 加锁设置超时时间: Lock.tryLock(time),超时自动释放
  • 减少锁粒度: 尽量不嵌套锁
  • 避免持有锁时再请求其他锁

十、MySQL 排他锁(FOR UPDATE)并发访问效果

问题: SQL 加排他锁 FOR UPDATE,第一个请求未执行完,第二个请求进来会继续执行还是阻塞?

答案:

锁类型场景第二个请求效果
行级排他锁查询命中索引,锁对应数据行访问同一行 → 阻塞等待;访问其他行 → 正常执行
表级排他锁查询未命中索引 / 索引失效,行锁升级为表锁所有后续请求都会阻塞

总结: 访问同一条数据必然阻塞;不同行、行锁生效则不阻塞。


十一、读锁、写锁(共享锁 & 排他锁)区别

问题: MySQL 读锁和写锁有什么区别?

读锁(共享锁 / LOCK IN SHARE MODE)

  • 共享: 多个线程可同时加读锁,互不阻塞
  • 互斥: 有加读锁时,所有写锁都会被阻塞
  • 用途: 保证查询数据期间不被修改

写锁(排他锁 / FOR UPDATE)

  • 独占: 只能有一个线程持有写锁
  • 互斥: 持有写锁后,其他线程的读锁、写锁全部阻塞
  • 用途: 更新、删除、强一致性查询场景

口诀: 读读共享、读写互斥、写写互斥


十二、MySQL 调优 & 索引数据结构

问题: MySQL 日常如何调优?索引使用什么数据结构?

12.1 MySQL 调优方向

优化方向具体措施
SQL 优化避免 SELECT *、大事务、全表扫描、隐式类型转换;用 EXPLAIN 分析执行计划
索引优化合理创建联合索引、遵循最左匹配,删除冗余 / 失效索引
架构优化主从复制、读写分离、分库分表
配置调优调整 Buffer Pool、连接数、超时时间等参数
存储引擎业务表统一使用 InnoDB

12.2 索引数据结构

InnoDB 主键索引、二级索引默认使用 B+ 树


十三、B+ 树特点 & 和 B 树的区别

问题: B+ 树有哪些特点?对比 B 树有什么不同?

B+ 树特点

  • 所有数据行 / 数据页都存在叶子节点,非叶子节点只存索引键,不存完整数据
  • 叶子节点通过双向链表串联,范围查询、排序效率极高
  • 树高度更低,磁盘 IO 次数更少,查询性能稳定

与 B 树核心区别

对比维度B 树B+ 树
数据存储非叶子、叶子节点都存完整数据仅叶子存数据
范围查询需要中序遍历,效率低叶子链表遍历,远快于 B 树
IO 次数节点存储数据多,索引少,树更高节点存储索引更多,树更矮,磁盘 IO 更少
查询效率单条查询速度不稳定所有查询都从根走到叶子,性能均衡

十四、设计模式 + 实际业务场景

问题: 开发中用过哪些设计模式?举一个场景、模式、解决的问题。

示例 1:策略模式

  • 场景: 订单支付(微信、支付宝、银行卡、余额多种支付方式)
  • 使用模式: 策略模式
  • 解决问题: 消除大量 if-else / switch 判断,新增支付方式只需新增策略类,符合开闭原则,代码易维护扩展

示例 2:单例模式

  • 场景: 全局配置类、工具类、连接池管理
  • 使用模式: 饿汉 / 懒汉单例
  • 解决问题: 保证全局只有一个实例,节省资源,统一全局配置

示例 3:工厂模式

  • 场景: 根据类型创建不同消息推送(短信、APP 推送、站内信)
  • 使用模式: 简单工厂 / 抽象工厂
  • 解决问题: 对象创建与业务逻辑解耦,统一创建入口

十五、千万级宽表多条件分页查询优化

问题: 千万级订单主表、上百字段、多查询条件 + 关联表,分页十几秒,如何优化?

分维度整体优化

优化维度具体措施
字段优化宽表拆分为主表 + 扩展表,分页查询只查必要字段,避免查询上百字段
索引优化根据高频查询条件建立联合覆盖索引,避免回表;优化关联表索引
分页优化深分页使用主键偏移分页(WHERE id > ? LIMIT 10)替代传统 LIMIT offset, size
SQL 优化简化多表关联,优先小表驱动大表;禁止不必要查询、排序

架构层面

  • 冷热数据分离: 历史订单归档到历史表
  • 引入 ES: 复杂多条件、模糊查询、分页全部走 ES,MySQL 仅做基础事务
  • 读写分离: 查询走从库,减轻主库压力
  • 缓存: 对高频固定条件的分页结果做缓存

十六、分页 COUNT 总数查询慢(全表扫描)优化

问题: 分页需要查 COUNT 统计总数,条件多变、全表检索很慢,索引已加,如何优化?

区分业务场景,非精准统计

方案说明
允许近似值使用 MySQL 信息统计表 information_schema.TABLES 获取粗略总数,速度极快
允许延迟更新定时任务把总条数、条件统计结果预计算存入 Redis / 统计表,前端直接读取

精准 COUNT 优化

方案说明
优先使用主键 / 非空唯一索引COUNT(主键) 优于 COUNT(*)COUNT(字段)
复杂多条件将统计逻辑下沉到 ES / OLAP 数据库,专门做聚合统计
分页兜底前端取消实时总数,使用「上拉加载更多」,不再查询 COUNT
分区表按时间分区,统计时只扫描对应分区,缩小扫描范围

十七、分库、分表的作用与解决的问题

问题: 为什么要分库?为什么要分表?分别解决什么问题?

分表(水平分表 / 垂直分表)

解决单表数据量过大问题(千万 / 亿级):单表数据越多,索引、查询、写入越慢,分表把数据拆分到多张结构相同的表,降低单表数据量,提升查询和写入性能。

  • 垂直分表: 将不常用的大字段拆分到扩展表,主表保留高频字段
  • 水平分表: 按规则(如 ID 取模、时间范围)将数据分散到多张结构相同的表

分库

解决单库连接数、IO、CPU 瓶颈问题:单库承载的并发连接数、磁盘 IO、CPU 计算都有上限,分库将数据分散到不同数据库实例,提升整体并发处理能力。

  • 垂直分库: 按业务模块拆分(如订单库、用户库、商品库)
  • 水平分库: 同一业务数据按规则分散到多个数据库实例
0个评论
点击登录,快来和大家讨论吧~
表情
图片
暂无评论
下载 APP