资讯动态

Java高频面试题拆解:Spring、MySQL、Redis与并发集合答题要点

发布时间:2026/10/29 13:18:25 来源:尧图企业网站定制
简介这份 Java 高频面试题 PDF 面向准备技术面试的 Java 开发者、应届求职者以及需要集中回顾框架与数据库知识点的初中级工程师围绕常见高频考点做系统梳理适合面试前查漏补缺和知识框架复盘。内容以 Spring Bean 生命周期、Spring MVC 执行流程、Spring Boot 自动装配原理为主线并延伸到 SQL 执行计划、常见优化技巧、EXPLAIN 关键字段、索引失效场景、MySQL 常见索引类型、BTree 底层结构与线程池核心参数等主题兼顾框架原理与数据库调优。压缩包内共 1 个 PDF 文件大小约 396KB轻量便携便于在电脑或移动端快速翻阅。已有 180 人学习说明其内容具备一定参考价值。读者可从中获得按主题归类的面试要点、关键流程拆解与常见问题提示如 Bean 四阶段、MVC 八步流转、自动装配条件判断、索引失效的典型写法及线程池拒绝策略用于对照复习和构建答题思路。1. Java 高频面试题一张卷子里真正拉开差距的是哪几块先给一个反直觉的判断Java 高频面试题里背得多不等于答得对真正卡人的是流程题里那句“谁返回给谁”以及“它在哪里会坏”。见过不少人能把 Spring Bean 生命周期的四个阶段倒背如流一追问afterPropertiesSet()和init-method谁先执行就哑了也能说出三级缓存的名字却解释不清构造注入为什么绕不过循环依赖。这张卷子的分值分布大致是Spring 占三成MySQL 与 Redis 占三成并发和集合占三成剩下是基础题。适合面试前一周想系统过一遍、或者每次答到一半就被打断的人。下面按模块把高频题、答题思路和翻车点一层层拆开。2. Spring 考点Bean 生命周期、循环依赖与事务失效的三条追问链2.1 Bean 生命周期四个阶段和两个扩展点Spring Bean 生命周期这类题面试官通常不满足于四个阶段的名字他要听的是顺序以及你知不知道中间插了哪些扩展点。主干是实例化反射或工厂方法创建对象→ 依赖注入属性值和其他 Bean→ 初始化InitializingBean的afterPropertiesSet()再调init-method指定的方法→ 销毁DisposableBean的destroy()再调destroy-method指定的方法。真正拉开差距的是几个追问构造方法在哪个阶段在实例化而且先于依赖注入。BeanPostProcessor在哪初始化前后各回调一次。PostConstruct在哪在afterPropertiesSet()之前。原型作用域的 Bean 销毁方法会被调用吗不会容器只负责创建不负责销毁。public class DemoBean implements InitializingBean, DisposableBean { public DemoBean() { System.out.println(1. 构造方法实例化); } Override public void afterPropertiesSet() { System.out.println(3. InitializingBean 的回调); } public void initMethod() { System.out.println(4. init-method 指定的方法); } Override public void destroy() { System.out.println(6. DisposableBean 的回调); } public void destroyMethod() { System.out.println(7. destroy-method 指定的方法); } }这段代码里第 2 步是属性注入第 5 步是BeanPostProcessor的后置处理执行顺序是 1 → 2 → 3 → 4 → 5 → 使用 → 6 → 7。initMethod和destroyMethod这两个方法名不是固定的要在 XML 的init-method属性或Bean(initMethod ..., destroyMethod ...)里声明不声明就不会被调用。这是很多人答得出来、配置却写错的地方。提示被问到“初始化方法执行顺序”时按“构造 → 注入 → 前置处理 →PostConstruct→afterPropertiesSet→init-method→ 后置处理”一条线说完比背四个阶段名有用得多。2.2 循环依赖三级缓存到底缓存的什么循环依赖先说结论字段注入和 setter 注入Spring 能自动处理构造注入处理不了。原因很直白构造注入要求对象在创建时就把依赖传进来而依赖的对象又反过来要它两边都没造完谁也等不到谁。字段和 setter 注入能绕过去是因为对象可以先实例化出来、把引用提前暴露出去属性稍后再填。三级缓存的分工要背清楚。一级singletonObjects存完整的单例 Bean二级earlySingletonObjects存实例化完成、还没走完初始化的半成品三级singletonFactories存的是ObjectFactory用来生成早期引用。查找顺序是一级 → 二级 → 三级三级拿到对象后放进二级同时从三级移除。为什么非要三级而不是两级因为要留一次“要不要生成代理”的决定权。如果只有二级提前暴露的就是原始对象后面 AOP 生成了代理别的 Bean 手里拿的还是原始对象代理就失效了。三级存工厂调用getObject()时才决定返回原始对象还是代理对象这个时机刚刚好。Service public class AService { Autowired Lazy private BService bService; public void doSomething() { bService.work(); } }Lazy打在字段上注入进去的是一个代理对象第一次真正调用bService.work()时才去容器里取真实对象循环依赖这条链就被推迟到运行时才闭合。这类题通常这么答先说结论“构造注入不能自动解决”再说三级缓存各自的职责最后补一句“最彻底的办法还是重构把循环依赖拆掉”面试官基本就满意了。2.3 事务失效面试官最常追的七个场景事务失效是 Spring 模块里失分最集中的地方因为它考的不是记忆是“你知不知道代理是怎么工作的”。下面这张表建议直接背下来答题时按“场景 → 原因 → 怎么改”三段说。失效场景根本原因回答时补一句非 public 方法代理只拦截 public 方法改成 public或改用编程式事务static / final 方法无法被代理重写不要在这些方法上加注解同类内部调用this调用绕过代理对象注入自身或用AopContext.currentProxy()异常被 try-catch 吃掉容器感知不到异常继续往上抛或手动标记回滚异常类型不匹配默认只回滚RuntimeException和Error配rollbackFor Exception.class传播行为配错REQUIRES_NEW会挂起外层事务想一起回滚就用REQUIRED多线程里调用事务上下文绑定在 ThreadLocal 上拆成独立事务或用编程式事务隔离级别这块四级要能对应到脏读、不可重复读、幻读三张脸隔离级别脏读不可重复读幻读READ_UNCOMMITTED会会会READ_COMMITTED不会会会REPEATABLE_READ不会不会会间隙锁可缓解SERIALIZABLE不会不会不会传播行为七个真正高频的是四个REQUIRED默认有就加入没有就新建、REQUIRES_NEW无论如何新建外层挂起、NESTED嵌套事务靠保存点回滚、SUPPORTS有就加入没有就非事务跑。被问到“子事务回滚会不会影响主事务”答案就在REQUIRES_NEW和NESTED的区别里前者是两个独立事务后者回滚到保存点主事务还能继续。2.4 Spring MVC 与自动装配背流程不如背“谁返回给谁”Spring MVC 的九步流程背名字没用要抓住一条主线除了控制器处理请求和视图渲染其余每一步都要回到前端控制器。客户端发 HTTP 请求 →DispatcherServlet拦截 →HandlerMapping按 URL 找处理器 →HandlerAdapter调用控制器方法 → 控制器执行业务逻辑、准备模型数据 → 返回视图名 →ViewResolver解析出真实视图 → 渲染 → 响应客户端。答题时把“核心枢纽是DispatcherServlet”这句话放在开头后面顺着说条理自然就出来了。自动装配考的是注解的拆解。SpringBootApplication是三个注解的组合SpringBootConfiguration说明当前类是配置类ComponentScan默认扫描启动类所在包及子包EnableAutoConfiguration开启自动配置。第三个是重点它底层通过Import导入AutoConfigurationImportSelector调用getAutoConfigurationEntry()再调getCandidateConfigurations()拿到候选配置类清单配合Conditional系列注解做条件判断条件满足才把Bean方法的返回值注册进 IOC 容器。拦截器和过滤器经常被放在一起问用一张表答最省事维度过滤器 Filter拦截器 Interceptor规范层次Servlet 容器级别框架层面如 Spring MVC实现机制函数回调反射、动态代理作用范围进入容器的所有请求框架管理的 Controller 请求生命周期随应用启动创建、关闭销毁随请求创建、请求结束销毁能否拿到方法参数不能能执行顺序先后执行顺序记一句Filter →preHandle→ Controller →postHandle→afterCompletion→ Filter 的后处理。被追问“为什么拦截器更灵活”答“它能拿到方法参数和目标对象适合做业务相关的预处理”比重复概念有用。3. MySQL 与 Redis 考点执行计划、索引失效和五种数据类型的答法3.1 EXPLAIN 十个字段先看 type再看 key 和 Extra执行计划是 SQL 优化的入口面试里通常这么问“一条慢 SQL 你怎么查”标准动作是在查询语句前加explain然后按列读。字段含义答题重点id查询序列号id 相同从上到下执行id 不同则大的先执行select_type查询类型SIMPLE、PRIMARY、SUBQUERY、UNION 等table涉及的表先看驱动表是哪张type访问类型性能从好到坏要能排序possible_keys可能用到的索引为空说明没匹配到索引key实际使用的索引为 null 就是没走索引key_len索引使用长度越短越好但要结合字段类型看ref与索引比较的列或常量常用于 join 场景rows预估扫描行数越小越好Extra额外执行信息出现 filesort、temporary 就要警惕type的性能排序必须背熟system const eq_ref ref range index ALL。const是主键或唯一索引等值匹配到一行eq_ref是 join 时被驱动表用主键或唯一索引匹配ref是非唯一索引等值匹配range是范围扫描index是遍历整棵索引树ALL是全表扫描。答到ALL就要补一句“需要检查 where 条件是否能命中索引”。Extra里三个信号最常考Using index是覆盖索引不用回表是好事Using filesort说明排序没用到索引顺序需要额外排序Using temporary说明用了临时表常见于 group by 和 distinct。-- 先看执行计划再决定加不加索引 EXPLAIN SELECT id, name FROM orders WHERE user_id 1001 AND status 1 ORDER BY create_time DESC;这条语句如果key显示为 null、type是 ALL、Extra里同时出现Using filesort和Using temporary就说明既没走索引又要额外排序。对应的处理是建(user_id, status, create_time)这样的组合索引让过滤和排序都落在同一棵索引上。参数上注意组合索引的列顺序等值条件放前面范围或排序字段放后面。3.2 索引失效的八种写法与改写话术索引失效不能只背“%前缀”一条面试官随便换个写法就能问住。常见的有八种索引列上使用函数或表达式、使用NOT或!、OR连接的条件有一边没索引、索引列参与运算、like前置百分号、组合索引不满足最左前缀、隐式类型转换、对索引列做null判断。-- 失效函数作用在索引列上 SELECT * FROM orders WHERE YEAR(create_time) 2024; -- 改写改成范围比较索引可用 SELECT * FROM orders WHERE create_time 2024-01-01 AND create_time 2025-01-01;函数写在列上索引里存的是原始值数据库没法直接比较只能逐行算索引自然用不上。改写成范围比较之后create_time上的索引就能走范围扫描。这个改写思路可以套用到大部分“函数导致失效”的题上。-- 失效phone 是 varchar传数字触发隐式转换 SELECT * FROM user WHERE phone 13800000000; -- 改写传字符串类型对齐 SELECT * FROM user WHERE phone 13800000000;隐式转换这条最容易在项目里踩坑因为 SQL 写出来不报错只是悄悄变慢。回答时补一句“字段类型和传参类型要对齐”面试官会认为你实际排查过。OR的改写也常考一边有索引一边没有整体会退化成全表扫描处理办法是给另一边也加索引或者拆成两条 SQL 用union all合并。分页场景则要注意深分页用limit偏移量很大时效率低可以改成基于上一页最后一条记录的值做条件查询。3.3 B 树三层千万行这个数字怎么算出来的B 树的题能算清楚那三层数字比背“多路平衡查找树”有说服力。一个非叶子节点默认 16KB一个 key 一般 8 字节一个指针一般 6 字节合计 14 字节16KB 除以 14 字节大约是 1170取整按 1000 算。第一层 1000 个索引项第二层就是 1000 × 1000 等于百万级第三层到千万级所以 B 树一般只有 2 到 4 层。结构上有两个关键点所有数据都存在叶子节点叶子节点之间用指针连成链表范围查询顺着链表走就行非叶子节点只存索引不存数据所以同样大小的节点能塞下更多索引项扇出更大层数更低磁盘 IO 更少。这也是它比 B 树更适合做数据库索引的原因B 树的非叶子节点也存数据扇出小树就更高。被追问“为什么不用哈希索引”答“哈希等值查询快但范围查询和排序用不了因为哈希之后顺序就丢了”。被追问“为什么不用红黑树”答“红黑树是二叉的层数高几千万数据要几十层磁盘 IO 次数太多”。这两句补上B 树这道题基本就闭环了。3.4 Redis 五种数据类型对应哪些业务场景Redis 的题很少单独问类型定义基本都是“这个场景你用什么结构”。五种基础类型对应场景要能脱口而出字符串做缓存和计数器哈希存对象的多个字段列表做队列或栈集合做去重和交并差有序集合做排行榜和带权重的队列。类型结构特点典型场景String任意字符串支持自增缓存、计数器、防重复提交的标记Hash字段-值对集合对象缓存部分字段更新List有序、可两端操作消息队列、任务队列Set无序、不重复去重、共同好友、标签ZSet带分数、按分数排序排行榜、延迟队列# 排行榜写入两个用户的分数 ZADD rank:score 95 user:1001 ZADD rank:score 88 user:1002 # 取分数最高的前三名WITHSCORES 表示连分数一起返回 ZREVRANGE rank:score 0 2 WITHSCORESZADD里分数可以重复成员不能重复ZREVRANGE按分数从高到低取0 2是闭区间下标。如果要取某个分数区间用ZRANGEBYSCORE。防重复提交那道题也会用到 Redis 的原子自增以“请求路径 参数 用户 id”拼成 key用incr自增返回 1 说明是第一次提交顺便设置 15 秒过期返回值大于 1 就说明重复了直接拦截。这么答既说了结构也说了原子性比只答“用 Redis 加锁”完整。4. 并发与集合考点线程池七个参数、三大特性和 HashMap 扩容4.1 线程池七个参数与四种拒绝策略线程池这道题能把七个参数按顺序说清楚并且知道任务的执行顺序是“核心线程 → 队列 → 救急线程 → 拒绝策略”就已经赢了一半。很多人顺序答反把救急线程说在队列前面这是硬伤。参数含义怎么调corePoolSize核心线程数按任务类型估IO 密集型可大于 CPU 核数maximumPoolSize核心 救急线程上限要和队列容量一起算keepAliveTime救急线程空闲存活时间短任务可设小避免占资源unit时间单位秒、毫秒等workQueue任务排队队列必须有界防止任务无限堆积threadFactory线程工厂给线程起名出问题好排查handler拒绝策略按业务选别默认抛异常了事ThreadPoolExecutor pool new ThreadPoolExecutor( 4, // 核心线程数 8, // 最大线程数 60L, TimeUnit.SECONDS, // 救急线程空闲存活时间 new ArrayBlockingQueue(200), // 有界队列容量 200 r - new Thread(r, order-pool),// 线程工厂统一命名 new ThreadPoolExecutor.CallerRunsPolicy() // 拒绝策略 );参数的含义在注释里标了重点说两个容易答错的点。第一队列必须是有界的无界队列会让maximumPoolSize形同虚设任务一直往队列里堆最后内存撑爆。第二拒绝策略四种要能对应场景AbortPolicy直接抛异常是默认值CallerRunsPolicy让提交任务的线程自己跑起到削峰作用DiscardOldestPolicy丢掉队列里最老的任务DiscardPolicy直接丢新任务不报错。被问到“为什么不用Executors创建线程池”标准答法是newFixedThreadPool和newSingleThreadExecutor用的是无界队列任务堆积会出问题newCachedThreadPool的最大线程数是整型上限高并发下会不断创建线程newScheduledThreadPool同样存在队列无界的问题。所以规范做法是自己new ThreadPoolExecutor把七个参数写明白。4.2 线程状态与优雅退出interrupt 和标志位怎么选Java 线程六种状态要能区分新建、可运行、阻塞、等待、超时等待、终止。重点在阻塞和等待的区别阻塞是在等锁等待是调用了wait()、join()或park()在等通知超时等待是带了超时参数的版本时间到了自己会恢复。线程池本身还有五种状态运行中、关闭、停止、整理、终止。shutdown()不再接受新任务但会把队列里已有的任务执行完shutdownNow()不再接受新任务尝试中断正在执行的任务并返回还没执行的任务列表。这两个的区别几乎每次都会问。public class Worker implements Runnable { // volatile 保证可见性主线程改了标志位工作线程能立刻看到 private volatile boolean running true; Override public void run() { while (running) { try { doWork(); } catch (InterruptedException e) { // 收到中断信号恢复中断标记后退出循环 Thread.currentThread().interrupt(); break; } } } public void stop() { running false; } }标志位适合循环体内能定期检查的场景改一下volatile变量就行。但如果线程正阻塞在sleep()、wait()、join()上标志位是看不到的必须调interrupt()让它抛InterruptedException醒过来。捕获异常后要恢复中断标记再退出不然上层调用者不知道发生过中断。这句细节能说出来说明真写过并发代码。4.3 原子性、可见性、有序性i 为什么不是原子的三大特性是并发题的根i是最经典的入口。它看着是一行实际是三步读i的值、加 1、写回。两个线程同时读到同一个旧值各自加完写回去结果就少加了一次。public class Counter { private int count 0; public void increment() { count; // 读、加、写三步任何一步都可能被其他线程插队 } public int getCount() { return count; } }解决办法分三档synchronized或Lock加锁最稳但性能一般AtomicInteger用 CAS 自旋适合竞争不激烈的计数LongAdder分段累加高并发下更好。回答时把三档都点一下比只说“加锁”显得有取舍。可见性讲的是一个线程改了共享变量另一个线程要能立刻看到。volatile保证每次读都从主内存拿最新值同时禁止指令重排所以双重检查锁的单例必须给它加volatile。但volatile不保证原子性i加了它照样出错这个反问经常被用来筛人。有序性则是编译器和 CPU 可能重排指令volatile的写-读会形成内存屏障把重排挡住。4.4 HashMap 与集合选型1.7 死循环那个坑HashMap 的结构演进要分两个版本说。JDK 1.7 及以前是数组加链表扩容迁移时用头插法多线程同时扩容可能把链表接成环之后get一个不存在的 key 就会在环上打转CPU 直接跑满。JDK 1.8 改成数组加链表加红黑树扩容用尾插法并且用高低位拆分迁移死循环这个问题就没了。但 1.8 的 HashMap 仍然不是线程安全的原因要能说三条并发put可能互相覆盖两个线程算到同一个下标后写的把先写的盖掉扩容期间读写交错可能读到旧数组导致数据丢失size字段不是原子更新计数不准。被追问“那用ConcurrentHashMap行不行”答“行1.8 里它用 CAS 加synchronized锁单个桶锁粒度比整表锁小”。集合选型题一般给场景让你挑下面这张表可以直接用集合底层结构有序性重复性适用场景ArrayList动态数组按插入顺序可重复随机访问多、增删少LinkedList双向链表按插入顺序可重复频繁在头尾增删也可当队列HashSet哈希表无序不可重复去重、判断存在LinkedHashSet哈希表 链表按插入顺序不可重复去重且要保留顺序TreeSet红黑树排序不可重复需要自然排序或自定义排序HashMap哈希表无序键不重复键值对快速查找LinkedHashMap哈希表 链表按插入或访问顺序键不重复需要顺序或做简易 LRUTreeMap红黑树按键排序键不重复需要按 key 排序遍历ArrayDeque动态数组按插入顺序可重复两端操作频繁的队列PriorityQueue二叉堆按优先级出队可重复每次取最小或最大4.5 高频基础题单例写法、Stream 常用 API 和接口区别单例模式五种写法能说出双重检查锁为什么加volatile这题就算过了。饿汉式在类加载时就创建线程安全但可能浪费资源懒汉式第一次请求才创建要自己处理线程安全双重检查锁加两次判空减少锁竞争静态内部类靠类加载机制保证线程安全同时实现懒加载枚举天然防反射和反序列化破坏是最简洁的写法。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; } }new一个对象分三步分配内存、初始化、把引用指向内存。指令重排可能让第三步排到第二步前面另一个线程在外层判空时就会拿到一个还没初始化完的对象。volatile把这三步的顺序固定住所以不能省。接口和抽象类的区别用一句话概括接口是纯抽象的类型声明一个类可以实现多个接口抽象类可以有字段和已实现的方法一个类只能继承一个。Stream 的常用 API 按用途分组记过滤filter、映射map和flatMap、排序sorted、去重distinct、截取limit和skip、聚合reduce和collect、判断anyMatch/allMatch/noneMatch、查找findFirst/findAny。面向对象三大特征各配一句人话继承是复用父类能力封装是藏起内部细节只露接口多态是同一接口不同实现。5. 易错点五类一开口就掉分的答法5.1 事务注解加在 private 方法上还振振有词错在哪把Transactional标在 private、static 或 final 方法上然后坚信事务会生效。为什么错Spring 的事务靠 AOP 代理实现代理只能重写 public 方法private 方法根本进不了代理逻辑注解等于白写。怎么答对先说“代理拦截不到非 public 方法”再说“要改成 public或者干脆用TransactionTemplate走编程式事务”。能主动提编程式事务说明你知道注解不是唯一解。5.2 线程池用 Executors 创建还说“方便”错在哪张口就是Executors.newFixedThreadPool(10)被问队列多长答不上来。为什么错这几个工厂方法把队列和最大线程数藏起来了固定大小线程池用的是无界队列缓存线程池的最大线程数是整型上限高峰期任务堆积或线程暴涨都发现不了。怎么答对直接说“生产环境自己new ThreadPoolExecutor七个参数显式写队列必须有界”然后把核心数、队列容量、拒绝策略的估算依据讲一遍。5.3 索引失效只背了“% 前缀”一条错在哪被问到“还有哪些情况索引会失效”只能重复like %xx。为什么错索引失效的触发点很多函数、运算、隐式转换、OR、最左前缀都会让索引用不上只背一条说明没实际排查过。怎么答对按“列上有没有被加工”这条主线展开函数和运算是一类类型转换是一类组合索引顺序是一类每类给一个改写示例。5.4 HashMap 线程不安全只说“没加锁”错在哪回答“HashMap 没加 synchronized 所以不安全”等于没说。为什么错面试官想听的是具体会坏在哪。怎么答对分版本说1.7 头插法扩容可能成环导致死循环1.8 改尾插法解决了环但仍然会有覆盖、丢数据、size不准这三个问题最后补一句“要并发就用ConcurrentHashMap”。5.5 循环依赖一律回答“三级缓存能解决”错在哪不管什么注入方式都说三级缓存能兜住。为什么错构造注入时对象还没实例化完没有引用可以提前暴露三级缓存也无能为力。怎么答对先分注入方式给结论字段和 setter 能自动处理构造注入不能再说三级缓存各自存什么最后补Lazy和重构两条路。6. 答题技巧把八股答成项目经验的四步模板八股题最怕答成背书面试官听两句就走神。一个能反复用的四步模板是先给结论再讲机制然后说一个坏掉的场景最后给修复方案。四步走完同一道题从“背过”变成“用过”。题目第一步结论句常见追问Bean 生命周期四个阶段两个扩展点决定顺序afterPropertiesSet和init-method谁先循环依赖字段和 setter 能解构造注入不能三级缓存各存什么事务失效代理拦不到的方法注解就是摆设同类调用怎么破索引失效索引列被加工过就用不上索引隐式转换怎么发现线程池七个参数顺序是核心、队列、救急、拒绝为什么不用 Executors“坏掉的场景”这一步最能拉开差距。比如讲线程池不要只报参数说一句“之前队列设成无界压测时任务一直堆最后内存告警后来改成有界队列加CallerRunsPolicy高峰期限本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑