ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

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

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

简介:这份 Java 高频面试题 PDF 面向准备技术面试的 Java 开发者、应届求职者以及需要集中回顾框架与数据库知识点的初中级工程师,围绕常见高频考点做系统梳理,适合面试前查漏补缺和知识框架复盘。内容以 Spring Bean 生命周期、Spring MVC 执行流程、Spring Boot 自动装配原理为主线,并延伸到 SQL 执行计划、常见优化技巧、EXPLAIN 关键字段、索引失效场景、MySQL 常见索引类型、B+Tree 底层结构与线程池核心参数等主题,兼顾框架原理与数据库调优。压缩包内共 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 WITHSCORES

ZADD里分数可以重复,成员不能重复;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哈希表 + 链表按插入或访问顺序键不重复需要顺序,或做简易 LRU
TreeMap红黑树按键排序键不重复需要按 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,高峰期限

本文还有配套的精品资源,点击获取

返回列表