十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Java后端面试100问:核心考点与项目场景题全解析

Java后端面试100问:核心考点与项目场景题全解析 秋招临近很多准备 Java 后端开发岗位的同学都在高强度刷题。但网上面试题零零散散今天看一篇集合明天翻一页 JVM后天又去补 Spring到最后脑子里全是碎片面试时一紧张连 volatile 和 synchronized 的区别都说不利索。这一篇我结合历年高频考点和近期 Java 面试必考题整理了一份100 问核心考点清单并且把每个模块里最容易考的“八股文”和“项目场景题”做了对照拆分。不是简单罗列问题而是告诉你每一类问题面试官到底想考什么、该怎么答、项目里怎么印证。文章内容会比较多建议先收藏再按章节刷。下面我们正式开始。1. 100 问的搭建逻辑与使用建议先说明这份 100 问是怎么组织的避免你拿到清单后不知道从哪看起。1.1 为什么面试必考题集中在这些模块Java 后端面试考察范围虽然广但核心模块非常固定基本可以分成七大类模块考察内容面试官意图Java 基础面向对象、String、集合、异常、泛型、反射考察语言基本功是否扎实集合框架HashMap、ConcurrentHashMap、ArrayList、LinkedList考察对常用数据结构的理解深度并发编程synchronized、volatile、线程池、锁、JMM考察并发场景下的设计和排错能力JVM内存区域、GC、类加载、OOM、性能调优考察是否具备线上问题排查能力Spring 系列IOC、AOP、Bean 生命周期、事务失效考察框架原理和项目落地能力MySQL索引、事务、锁、SQL 优化后端开发最核心的持久层基本功Redis缓存、持久化、分布式锁、缓存一致性考察高性能场景设计能力项目场景题不会单独割裂而是嵌入到这些模块中。比如问完线程池参数马上会追问“你项目里线程池参数怎么设置的”问完索引底层马上追问“你项目里慢 SQL 怎么优化的”。1.2 如何在一周内刷完 100 问标题说“一周刷完”这里的“刷”不是让你背答案而是建立考点索引 画出知识盲区所以建议按下面节奏推进第 1-2 天Java 基础 集合框架配合手写源码片段。第 3-4 天并发编程 JVM这两块必须搭配代码验证。第 5 天Spring MySQL重点做事务失效场景复盘。第 6 天Redis 项目场景题把前面所有知识点串到项目里。第 7 天整体复盘尝试不看答案复述每个问题的答题框架。不建议一上来就背“200W 字面试宝典”信息量太大反而记不住。我更推荐先把本文的 100 问过一遍发现自己哪里卡壳再回到详细笔记里查漏补缺这样效率最高。2. Java 基础高频考点不只是语法很多同学觉得 Java 基础简单真正面试时却容易在细节上翻车。这一部分挑选 5 个最常考的问题做详细拆解并给出现场答题思路。2.1 String、StringBuilder、StringBuffer 的区别这是 Java 基础里的老八股但每次面试几乎都会被问到。面试官不是只想知道区别更想确认你在字符串拼接时是否有性能意识。先看三者核心区别String 是不可变对象每次拼接都会生成新的字符串对象频繁拼接会产生大量垃圾对象。StringBuilder 是可变的适合单线程下的字符串拼接性能最高。StringBuffer 是线程安全的关键方法加了 synchronized适合多线程环境但性能略低于 StringBuilder。常考代码题是这样的String s ; for (int i 0; i 10000; i) { s i; // 每次循环都会创建新的 String 对象 }这段代码在循环中拼接 1 万次会创建大量中间对象性能很差。正确写法是StringBuilder sb new StringBuilder(); for (int i 0; i 10000; i) { sb.append(i); } String result sb.toString();加分回答可以提一句JDK 编译期对做了优化但那是针对字面量拼接循环中的动态拼接依然会频繁创建对象。项目里如果追求性能建议直接使用StringBuilder。2.2 equals 和 hashCode 为什么要一起重写这个问题几乎必考而且经常以“为什么重写 equals 必须重写 hashCode”的形式出现。核心在于 HashMap、HashSet 这类散列集合的存储逻辑存入对象时先根据 hashCode 计算桶位置。如果桶位置相同再用 equals 比较对象内容是否一致。如果不重写 hashCode两个内容相同的对象会计算出不同的哈希值导致被放入不同桶HashSet 就无法去重。举一个反例public class User { private String name; public User(String name) { this.name name; } Override public boolean equals(Object obj) { if (this obj) return true; if (obj null || getClass() ! obj.getClass()) return false; User user (User) obj; return Objects.equals(name, user.name); } }这里重写了 equals但没有重写 hashCode。执行下面的代码SetUser set new HashSet(); set.add(new User(张三)); set.add(new User(张三)); System.out.println(set.size()); // 输出 2而不是 1因为两个对象的 hashCode 不同所以即使 equals 返回 trueHashSet 也认为它们是不同元素。正确做法是同时重写 hashCodeOverride public int hashCode() { return Objects.hash(name); }面试时如果能结合 HashMap 的 put 流程来回答会显得理解更深入。2.3 泛型中的 extends 和 super 怎么理解泛型是 Java 基础里比较抽象的部分面试常考 PECS 原则即Producer Extends, Consumer Super。简单解释? extends T可以读但不能写适合从集合中取数据。? super T可以写但不能读适合往集合中放数据。看一个典型代码List? extends Number list1 new ArrayListInteger(); // list1.add(1); // 编译报错因为无法确定到底是 Integer 还是 Long List? super Integer list2 new ArrayListNumber(); list2.add(1); // 可以写入 // Number num list2.get(0); // 编译报错读取时只能拿到 Object项目中更常见的是 Java 集合工具类的泛型定义。比如Collections.copy的定义是void copy(List? super T dest, List? extends T src)目的就是保证源列表可以读、目标列表可以写。2.4 反射的用途和性能问题反射是 Spring 框架的底层基石面试时不会只问“反射是什么”更常问“你在哪里用过反射”和“反射为什么慢”。先说用途Spring IOC 容器通过反射创建对象。Spring AOP 通过反射或动态代理生成代理类。MyBatis 通过反射将数据库结果映射到 Java 对象。通用工具类通过反射获取类上的注解、方法、字段。反射慢的原因主要有三点动态解析类型比直接调用多一步、编译器无法优化反射调用、频繁反射会产生大量临时对象。不过 JDK 在不断优化反射性能现代框架也通过缓存Method对象来规避一部分开销。项目场景题可以这样答在项目里开发了一个通用的 Excel 导出工具通过反射读取对象字段上的ExcelField注解动态导出指定列避免为每个实体类单独写导出逻辑。2.5 异常体系与 finally 的执行时机异常问题通常结合代码题出现比如“try 里有 returnfinally 还会执行吗”。先看体系Throwable下面分Error和Exception。Error是 JVM 层面的严重错误比如OutOfMemoryError、StackOverflowError程序无法恢复。Exception分为受检异常和非受检异常RuntimeException属于非受检异常编译器不强制处理。关于 finally关键点是finally 块在 return 之前执行。如果 finally 里有 return会覆盖 try 里的 return。不要在 finally 里放耗时操作或再次抛出异常否则会影响主流程。举例public static int test() { try { return 1; } finally { System.out.println(finally 执行了); } }输出结果是“finally 执行了”方法返回 1。如果 finally 中也有 return则 try 里的返回值会被覆盖。这一点在面试代码题里非常容易考。3. 集合框架核心HashMap 是必考重点集合模块中HashMap 的出镜率最高从存储结构、put 流程、扩容机制到线程安全性都能连环追问。3.1 HashMap 的底层存储结构JDK 8 中 HashMap 的底层是“数组 链表 红黑树”。数组的每个位置叫桶。当多个 key 的哈希值映射到同一个桶时通过链表解决冲突。当链表长度超过 8 且数组长度超过 64 时链表转为红黑树降低查询复杂度。关键参数参数默认值说明initialCapacity16数组初始容量loadFactor0.75负载因子TREEIFY_THRESHOLD8链表转红黑树阈值UNTREEIFY_THRESHOLD6红黑树转链表阈值负载因子 0.75 是时间和空间成本的折中方案。负载因子过大哈希冲突概率高查询变慢过小则数组利用率低频繁扩容浪费空间。3.2 put 方法的完整流程这是面试手撕频率最高的问题。完整流程可以按下面几步描述对 key 的 hashCode 做扰动计算让高位也参与散列降低碰撞概率。根据哈希值和数组长度计算桶下标。如果桶为空直接放入新节点。如果桶不为空遍历链表或红黑树用 equals 判断是否存在相同 key。如果存在相同 key覆盖旧值并返回旧值。如果不存在插入新节点。插入后判断链表长度是否达到 8数组长度是否达到 64决定是否转红黑树。最后判断元素数量是否超过扩容阈值如果超过则扩容。简化代码示例如下仅演示流程思路不是 JDK 真实源码final V putVal(int hash, K key, V value) { NodeK, V[] tab; int n, i; if ((tab table) null || (n tab.length) 0) { n (tab resize()).length; } // 计算桶位置 if ((p tab[i (n - 1) hash]) null) { tab[i] newNode(hash, key, value, null); } else { // 处理哈希冲突 } return null; }面试时不需要一字不差背源码但要把“计算下标、哈希冲突处理、链表转红黑树、扩容”这几个关键节点说清楚。3.3 ConcurrentHashMap 如何保证线程安全HashMap 在多线程下扩容会出现死循环或数据丢失所以并发环境下要使用 ConcurrentHashMap。JDK 7 的 ConcurrentHashMap 使用分段锁将数据分成多个 Segment每个 Segment 持有自己的锁。JDK 8 放弃分段锁改为 CAS synchronized 锁住数组中的每个桶。JDK 8 的 put 流程简述计算 key 的桶位置。如果桶为空使用 CAS 直接原子插入元素。如果桶不为空使用 synchronized 锁住头节点再执行插入。支持多线程并发扩容提升扩容效率。记住一个结论ConcurrentHashMap锁粒度更细并发度更高Redis 分布式缓存、本地缓存设计时经常提到它。3.4 ArrayList 和 LinkedList 怎么选这两个集合也经常被拿来对比ArrayList 底层是数组随机访问是 O(1)插入删除需要移动元素。LinkedList 底层是双向链表插入删除是 O(1)但随机访问是 O(n)。项目里大多数场景用 ArrayList因为遍历和随机访问更重要而且 LinkedList 的节点对象更占内存。再补充一个容易忽略的点ArrayList 频繁在指定位置插入元素时会触发System.arraycopy如果数据量大性能会明显下降。这也是为什么队列场景常使用ArrayDeque而非LinkedList。4. 并发编程线程池和锁是重点并发编程是 Java 面试的分水岭答得好能拉开很大差距。下面拆解四个必考题。4.1 synchronized 和 ReentrantLock 的区别先画一张对比表对比项synchronizedReentrantLock锁类型内置锁API 级锁获取释放JVM 自动手动 lock/unlock可中断不支持支持公平锁非公平可设置公平条件变量只有一个可绑定多个 Condition性能JDK 6 后优化明显高并发灵活度更高日常开发中大多数场景用 synchronized 就够了写法简单且不会因为忘记解锁导致死锁。只有在需要超时等待、可中断、多个条件队列时才考虑 ReentrantLock。加分回答可以提一句JDK 6 之后synchronized 引入偏向锁、轻量级锁、重量级锁的升级路径锁性能已经大幅提升。4.2 volatile 的原理与适用场景volatile 是并发编程中最重要的关键字之一它保证了两点可见性一个线程修改变量后其他线程能立即看到最新值。有序性禁止编译器重排序和 CPU 指令重排序。但它不保证原子性。比如count这种“读-改-写”操作即使变量是 volatile多线程下依然不安全。经典用法是状态标志位public class TaskManager { private volatile boolean running true; public void stop() { running false; } public void run() { while (running) { // 执行任务 } } }另一个典型场景是单例模式的双重检查锁单例实例用 volatile 修饰防止指令重排序导致其他线程拿到未初始化完成的对象。4.3 线程池的核心参数线程池七参数是必背内容参数说明corePoolSize核心线程数maximumPoolSize最大线程数keepAliveTime非核心线程空闲存活时间unit存活时间单位workQueue任务队列threadFactory线程工厂handler拒绝策略执行流程要会描述如果核心线程数没满创建核心线程执行任务。如果核心线程数满了任务放入阻塞队列。如果队列满了创建非核心线程执行任务。如果线程数达到 maximumPoolSize执行拒绝策略。常见的拒绝策略有四种AbortPolicy抛异常、CallerRunsPolicy调用者线程执行、DiscardPolicy直接丢弃、DiscardOldestPolicy丢弃最老任务。实际项目中建议用ThreadPoolExecutor自定义线程池不要用 Executors 的现成方法因为newFixedThreadPool的队列是无限长的高并发下可能 OOM。一个自定义线程池示例ThreadPoolExecutor executor new ThreadPoolExecutor( 4, // 核心线程数 8, // 最大线程数 60L, TimeUnit.SECONDS, // 空闲回收时间 new ArrayBlockingQueue(1000), new ThreadFactoryBuilder().setNameFormat(order-pool-%d).get(), new ThreadPoolExecutor.CallerRunsPolicy() );线程工厂推荐使用 Guava 或 Hutool 的 NamedThreadFactory 给线程命名方便线上排查。4.4 ThreadLocal 原理与内存泄漏ThreadLocal 用于线程私有变量存储但面试高频考点是内存泄漏。原理简要说明每个线程内部持有ThreadLocalMapMap 的 key 是 ThreadLocal 的弱引用value 是强引用。当 ThreadLocal 外部强引用被置为 null 后key 会被回收但 value 无法访问如果线程存活时间很长value 就会一直存在。最佳实践是每次用完 ThreadLocal 后在 finally 块中调用remove()ThreadLocalInteger threadLocal new ThreadLocal(); try { threadLocal.set(1); // 业务逻辑 } finally { threadLocal.remove(); }特别是线程池场景线程是复用的如果不及时 remove下一次任务会读到上一次任务的残留数据产生业务数据串线问题。这是非常经典的项目场景题。5. JVM内存区域、GC 与 OOM 排查JVM 是 Java 面试中的硬骨头也是区分初中级面试者的重要模块。重点掌握以下几个方向。5.1 JVM 运行时数据区域JVM 内存区域按线程是否共享划分区域是否共享存储内容异常程序计数器线程私有当前线程执行字节码行号无虚拟机栈线程私有局部变量表、操作数栈StackOverflowError本地方法栈线程私有native 方法调用StackOverflowError堆线程共享对象实例和数组OutOfMemoryError方法区线程共享类信息、常量、静态变量OutOfMemoryErrorJDK 8 中方法区被元空间替代元空间使用本地内存默认不再受 JVM 堆内存限制。这也是为什么许多项目会配置-XX:MaxMetaspaceSize来避免元空间无限制增长。5.2 如何判断对象可以被回收两种常见算法引用计数法对象被引用计数为 0 时回收。但无法解决循环引用问题所以 JVM 没用它。可达性分析算法从 GC Roots 向下搜索搜索路径叫引用链。不可达对象会被标记回收。GC Roots 包括虚拟机栈中引用的对象、静态变量引用的对象、常量引用的对象、本地方法栈中 JNI 引用的对象等。5.3 常见垃圾回收器与内存分配策略面试常问的垃圾回收器有 Serial、ParNew、CMS、G1。当前主流 JDK 中 G1 使用广泛它把堆划分为多个 Region可以设置停顿时间目标兼顾吞吐量和响应时间。关于内存分配策略记住几个基本原则新对象优先分配在 Eden 区。大对象直接进入老年代。长期存活的对象会自动晋升到老年代。Minor GC 频繁触发时可能导致动态年龄判断提前晋升。5.4 线上 OOM 排查思路很多同学听到 OOM 就慌其实排查思路是固定的。这里以线上报错java.lang.OutOfMemoryError: Java heap space为例梳理完整排查流程。第一步保留现场。JVM 启动参数增加-XX:HeapDumpOnOutOfMemoryError -XX:HeapDumpPath/data/logs/这样发生 OOM 时JVM 会自动生成堆转储文件。第二步查看进程状态。通过jstat观察 GC 频率jstat -gcutil pid 1000如果 Full GC 非常频繁且回收效果差多半是内存泄漏或堆设置过小。第三步用 MAT 或 VisualVM 分析堆转储文件查看大对象、类加载器、JDBC 连接等是否出现异常引用。第四步如果是代码问题常见的根因包括一次性查询数据量过大、未关闭连接、静态集合持有大量对象、ThreadLocal 未清理、字符串拼接产生大量中间对象。从这个角度回答“线上 OOM 怎么排查”比单纯背内存区域更符合项目场景题要求。6. Spring 与 Spring Boot 高频考点Spring 系列是 Java 后端开发的重头戏面试中通常不会直接问“IOC 是什么”而是结合代码分析 Bean 生命周期、事务失效等问题。6.1 IOC 与 AOP 的通俗理解IOC控制反转核心思想是把对象的创建和管理交给 Spring 容器而不是开发者自己 new。好处是解耦类之间只依赖接口和配置模块替换成本降低。AOP面向切面编程用于处理日志、事务、权限等横切逻辑。项目里最常见的用法是登录校验切面和使用Transactional声明式事务。一个简单的 AOP 日志切面示例Aspect Component public class LogAspect { Around(annotation(logAnnotation)) public Object doAround(ProceedingJoinPoint joinPoint, LogAnnotation logAnnotation) throws Throwable { long start System.currentTimeMillis(); try { return joinPoint.proceed(); } finally { long cost System.currentTimeMillis() - start; System.out.println(方法执行耗时 cost ms); } } }6.2 Bean 生命周期Bean 生命周期是 Spring 八股中的高频题。简化后记住这条主线实例化通过构造器创建 Bean 对象。属性填充通过依赖注入填充属性。初始化前执行 BeanPostProcessor 的前置方法。初始化执行afterPropertiesSet或自定义init-method。初始化后执行 BeanPostProcessor 的后置方法AOP 代理在此阶段生成。使用Bean 被业务代码正常使用。销毁容器关闭时执行销毁方法。回答时可以强调AOP 代理对象是在 Bean 初始化之后生成的所以你拿到的 Bean 可能是代理对象。6.3 Spring 事务失效的几种场景这是 Spring 面试中非常实用的项目场景题。下面列出事务失效的高频原因方法没有被 Spring 管理类上没有Service、Component等注解。Transactional加在非 public 方法上Spring 默认只对 public 方法代理。方法内通过this调用同类中的另一个被Transactional修饰的方法事务注解不生效。底层数据表使用 MyISAM 引擎不支持事务。异常被 try-catch 捕获事务感知不到异常不会回滚。抛出的是受检异常且没有指定rollbackFor默认只回滚RuntimeException和Error。多线程调用事务是在调用线程里提交的另一个线程无法被同一个事务管理。正确写法是Transactional(rollbackFor Exception.class) public void createOrder(Order order) { orderMapper.insert(order); }如果同一个类内部调用需要拆到不同 Bean 或使用TransactionTemplate手动控制public void createOrder(Order order) { transactionTemplate.execute(status - { orderMapper.insert(order); return null; }); }6.4 Spring Boot 自动配置原理自动配置是 Spring Boot 的核心特性。回答主线如下SpringBootApplication是一个组合注解包含SpringBootConfiguration、EnableAutoConfiguration、ComponentScan。EnableAutoConfiguration通过AutoConfigurationImportSelector加载META-INF/spring.factories或较新版本的AutoConfiguration.imports里配置的自动配置类。自动配置类上通常带有ConditionalOnClass、ConditionalOnMissingBean等条件注解只有满足条件时才生效。实际项目中我们经常通过ConfigurationProperties绑定application.yml中的自定义配置再通过 starter 的方式提供给其他服务使用这就是自定义 starter 的思路。7. MySQL 必考题索引、事务与 SQL 优化数据库是后端面试的重头戏尤其 MySQL几乎是每场必问。7.1 索引失效的常见场景索引是 MySQL 性能优化的重要手段但索引失效问题经常出现。以下是最常考的场景对索引列使用函数比如WHERE YEAR(create_time) 2025。对索引列进行隐式类型转换比如字符串字段直接传数字。使用LIKE %keyword前置模糊查询。联合索引不满足最左前缀原则。索引列参与算术运算。使用OR连接非索引列条件。加分回答如果你能明确说出“联合索引 (a, b, c) 在查询条件只包含 b 和 c 或只包含 c 时无法走索引必须包含 a”面试官会认为你理解底层。7.2 事务隔离级别MySQL 的四种隔离级别需要背熟隔离级别脏读不可重复读幻读READ UNCOMMITTED可能可能可能READ COMMITTED不可能可能可能REPEATABLE READ不可能不可能可能InnoDB 可避免SERIALIZABLE不可能不可能不可能面试时强调一点MySQL 默认隔离级别是 REPEATABLE READ但通过多版本并发控制MVCC和间隙锁可以在该级别下避免幻读。实际项目场景题常问“如何保证查询时的数据一致性”这时可以引出 MVCC说明常见数据库框架是如何通过快照读和当前读来解决读写并发问题的。7.3 聚簇索引与回表查询InnoDB 的索引分为聚簇索引和二级索引。聚簇索引的叶子节点存储整行数据二级索引的叶子节点存储主键值。如果查询条件使用二级索引但查询的列不在索引中就需要先查到主键再根据主键回表查询完整数据这个过程叫回表。覆盖索引指的是查询的列全部包含在索引中无需回表性能更好。比如表结构CREATE TABLE user ( id bigint NOT NULL AUTO_INCREMENT, name varchar(50) NOT NULL, age int NOT NULL, phone varchar(20) NOT NULL, PRIMARY KEY (id), KEY idx_name_age (name, age) ) ENGINEInnoDB;下面这条 SQL 只需返回 name 和 age索引完全覆盖不会回表SELECT name, age FROM user WHERE name 张三;而SELECT phone FROM user WHERE name 张三则需要回表。7.4 慢 SQL 优化思路慢 SQL 优化是项目场景题的重灾区建议按以下顺序回答通过慢查询日志定位问题 SQL。使用EXPLAIN分析执行计划重点看 type、key、rows、Extra。检查是否走索引有没有索引失效。检查是否查询了不必要字段避免SELECT *。大偏移量分页时考虑延迟关联。延迟关联示例-- 不推荐大偏移量MySQL 需要扫描并丢弃大量数据 SELECT * FROM orders ORDER BY id LIMIT 100000, 20; -- 推荐先查询主键再关联原表 SELECT o.* FROM orders o INNER JOIN (SELECT id FROM orders ORDER BY id LIMIT 100000, 20) t ON o.id t.id;8. Redis 必考题缓存与分布式场景Redis 在 Java 后端项目中几乎是标配重点掌握缓存设计三兄弟、持久化和分布式锁。8.1 缓存穿透、击穿、雪崩这三个概念必须区分清楚问题现象解决方式缓存穿透查询不存在的数据缓存和数据库都没有缓存空值、布隆过滤器、参数校验缓存击穿热点 key 过期大量请求打到数据库互斥锁、逻辑过期、热点 key 不过期缓存雪崩大量 key 同时过期或 Redis 宕机过期时间加随机值、集群高可用、限流降级缓存穿透在项目里最容易被问到比如“恶意请求频繁查询一个不存在的用户 ID”常规解决方式是布隆过滤器BlommFilterString filter new BloomFilter(1000000, 0.01); if (!filter.contains(userId)) { return null; }8.2 缓存与数据库一致性这是一个非常经典的项目场景题。常见的方案有两种先更新数据库再删除缓存。大多数场景推荐简单且可靠。延迟双删先删除缓存再更新数据库休眠一小段时间后再次删除缓存。延迟双删示例public void updateUser(User user) { // 第一次删除缓存 redisTemplate.delete(user: user.getId()); // 更新数据库 userMapper.updateById(user); // 休眠片刻确保读请求把旧缓存写回 Thread.sleep(100); // 第二次删除缓存 redisTemplate.delete(user: user.getId()); }但这个方案在极端并发下仍有问题更严谨的做法是结合 binlog 订阅方式同步删除缓存。面试时说出方案的优缺点即可不必追求银弹。8.3 Redis 分布式锁分布式锁的面试题已经从“怎么实现”升级到“有什么坑”。回答从最简单版本逐步深入第一版使用SETNX加锁设置过期时间Boolean success redisTemplate.opsForValue() .setIfAbsent(lock:order, token, 30, TimeUnit.SECONDS);第二版解决锁误删问题。删除前比对线程标识String token UUID.randomUUID().toString(); redisTemplate.opsForValue().setIfAbsent(lock:order, token, 30, TimeUnit.SECONDS); try { // 业务代码 } finally { Object value redisTemplate.opsForValue().get(lock:order); if (token.equals(value)) { redisTemplate.delete(lock:order); } }第三版引入 Redisson 看门狗机制解决锁自动续期问题。回答时能提到RedissonLock的 watch dog 会自动续期说明你实践过真实项目。8.4 Redis 持久化与过期删除策略Redis 持久化方式有两种RDB生成快照文件恢复速度快但可能丢失最后一次快照后的数据。AOF记录每次写命令数据更安全但文件较大恢复速度较慢。生产环境通常两种同时开启用 AOF 保证数据安全用 RDB 做快速恢复。过期删除策略要区分“惰性删除”和“定期删除”。Redis 会定期随机抽取过期 key 删除同时访问 key 时也会检查是否过期。这就是为什么 Redis 能平衡 CPU 和内存消耗。9. 项目场景题答题模板从八股到实战最后一部分重点解决很多人的痛点题目都会背但项目一问就卡壳。9.1 场景题 vs 八股文的区别八股文问的是“什么”场景题问的是“怎么办”。比如八股文synchronized 和 ReentrantLock 有什么区别。场景题你们项目里的秒杀系统是怎么防止超卖的。如果只背区别不会应用场景题就很难拿高分。所以强烈建议准备项目时按照“背景-方案-结果-踩坑”四段式梳理。9.2 一个完整场景题回答示例问题项目里的缓存和数据库一致性是怎么保证的回答框架背景我们是一个订单查询服务订单表数据量增长很快数据库压力大所以引入了 Redis 缓存热点订单数据。方案采用“先更新数据库再删除缓存”的策略。更新订单状态时先执行 SQL 更新再删除缓存中对应 key。下次请求查询时会回源数据库并重建缓存。结果接口响应时间从平均 250ms 降低到 40ms 左右数据库 QPS 明显下降。踩坑一开始我们用的是“先删缓存再更新数据库”结果在并发场景下可能出现旧数据被重新写入缓存的问题。后来改成“先更新库再删缓存”同时给缓存设置了合理的过期时间问题得到缓解。极端场景下还考虑了 binlog 订阅异步删除方案因为改动成本较高暂时还没有上线。这种回答方式既有技术方案又有过程思考比单纯说“用延迟双删”更有说服力。9.3 高频项目场景题清单以下是面试中出现频率最高的项目场景题可以结合自己的项目准备场景题核心考点项目里 Redis 在哪些场景使用缓存、分布式锁、计数器、排行榜接口性能优化怎么做的SQL 优化、Redis 缓存、异步化、批量处理超卖问题如何解决乐观锁、Redis 原子操作、唯一约束订单超时未支付如何关闭延迟队列、定时任务扫表、Redis 过期监听幂等性如何保证唯一索引、Token 机制、乐观锁版本号大文件上传怎么处理分片上传、断点续传、合并校验消息队列怎么保证不丢消息生产者确认、持久化、消费者手动 ACK分布式事务怎么解决本地消息表、TCC、最终一致性每一个场景题都要在项目里找到对应的代码位置哪怕只是一个小模块也比“我没做过”强得多。10. 一周冲刺复习计划与高频问题速查最后给出一周冲刺计划供你直接执行。10.1 七天复习路线天数复习模块产出物Day 1Java 基础 集合手写 HashMap put 流程、String 拼接对比Day 2并发编程手写单例双重检查锁、自定义线程池Day 3JVM画出内存区域、梳理 OOM 排查流程Day 4Spring梳理 Bean 生命周期、事务失效场景Day 5MySQLExplain 练习、慢 SQL 优化案例Day 6Redis 项目题目整理 10 个场景题回答模板Day 7整体复盘口述 100 问中的每一问10.2 面试现场急救清单如果在面试中被问到了不会的问题先别慌。建议按以下顺序应对先说出你对问题的理解哪怕不完全准确。再讲相关知识点比如问“红黑树”可以先讲 BST 和 AVL。坦诚说明项目里最远用到哪一步。反问面试官问题的具体场景把问题引导到熟悉的领域。以下是一些使用频率极高的速查问题适合作为每天早晚的巩固内容问题速记要点String 为什么不可变char 数组用 final 修饰且不提供修改方法HashMap 为什么线程不安全多线程扩容可能形成环数据可能丢失线程池拒绝策略怎么选对重要任务用 CallerRunsPolicy对可丢弃任务用 DiscardPolicyvolatile 为什么不保证原子性只保证可见性和有序性不保证复合操作Spring Bean 默认单例为什么安全无状态 Bean 没有共享可变数据MySQL 默认隔离级别REPEATABLE READ索引为什么用 B 树矮胖、范围查询高效、叶子节点链表顺序访问Redis 为什么快基于内存、单线程避免竞争、IO 多路复用缓存穿透与击穿的区别穿透是查不存在数据击穿是热点 key 过期如何排查 Full GC 频繁jstat 看 GC 频率dump 分析大对象无论你是 25 届还是 26 届这份 100 问清单都值得反复过几轮。真正拉开差距的不是背了多少题而是能不能把每个知识点讲成“项目里我是怎么用的”。如果这份清单对你有帮助可以收藏下来按七天计划逐项突破。也欢迎在评论区分享你自己的项目场景题和答题思路一起查漏补缺少走弯路。
返回列表