2026年Java面试到底该怎么准备,这个标题几乎每年都会流传一遍“最强八股文”版本。我的态度一直很直接:八股文不是救命稻草,但它是最省力地把知识框架搭起来的方式。Java面试再往前演进,底层原理、并发、JVM、数据库、Redis、分布式这些硬核问题永远是主战场,而八股文恰恰是一个“索引”,让你在看到任何一道稍微偏门的题目时,不至于大脑空白。这篇文章不是给你一份死记硬背的题库,而是把我这些年反复见过的Java高频题、高频追问点、容易踩的坑,按面试官真正想考察的能力重新整理了一遍。适合马上要参加实习秋招的应届生,也适合想跳槽的初中级开发,花两三天把章节过一遍,再去针对薄弱点深挖源码,效率会高很多。
1. 为什么2026年的Java面试还在问八股文
很多人抱怨面试只会问八股,但站在面试官视角看,八股其实是筛选性价比最高的工具。一场面试只有不到一小时,面试官不可能看完整套源码,大家默认用“基础概念+追问原理”的方式快速判断候选人的技术底线。所以那些说“八股没用”的人,往往是自己连底层都没搞清楚。
1.1 八股文不再是背诵,而是追问链的起点
2026年的Java面试已经很少直接问“HashMap的底层结构是什么”这种送分题,更多是“HashMap为什么用红黑树”“什么时候触发树化”“树化后为什么还要退化链表”。这背后是一条追问链,你答出第一层只是及格,能顺着往下说第二层第三层才是加分项。八股文背下来只是第一步,关键要理解每句话背后的“为什么”。比如红黑树的引入是为了解决极端哈希冲突下链表过长导致的O(n)查询,但红黑树节点占用空间更大,所以阈值设定为8,是为了让链表长度在正常负载因子下几乎不可能达到这个值。这一整条逻辑就是面试官想听到的。
1.2 给自己搭建一个知识“抽屉”
准备面试最忌讳零散地刷题,今天看线程池,明天看MySQL索引,最后合上书全忘了。更高效的做法是先建立一个知识分类表,把Java面试分成几个固定板块,每个板块下面对应若干核心考点。我习惯把优先级划成三档:
- 第一优先级:Java集合、并发、JVM、MySQL索引与事务,几乎任何公司必问。
- 第二优先级:Spring IoC/AOP、Spring Boot自动配置、Redis缓存设计,业务开发岗位命中率极高。
- 第三优先级:消息队列、分布式事务、限流熔断、系统设计,中高级岗位或者第二轮技术面常出现。
准备时按这个顺序推进。第一优先级的内容不能只背结论,要能说出原理和实验场景;第二优先级要结合工作里的真实用法;第三优先级更多是考察方案选型和综合设计能力。有了这个抽屉结构,再看到任何新题都知道该放进哪个分类,知识就会越攒越成体系。
2. Java基础高频考点:从String到HashMap再到equals/hashCode
基础部分是真正的试金石。这里如果答得含糊,面试官基本会直接判基础不牢,后续问题会越来越难。下面几个问题是我面试别人时几乎必问的,也是候选人最容易翻车的。
2.1 String、常量池与不可变性的完整回答思路
String问题经典到不能再经典,但能答完整的人很少。首先String是被final修饰的,底层在JDK 9之后的实现是byte数组加一个编码标志Coder,而不是以前的char数组,这是为了避免每个字符都占两个字节,纯英文场景下可以省一半内存。String不可变的三个好处要记牢:线程安全、字符串常量池可以安全复用、保证hashCode的稳定性。然后就是双等号和equals的区别,String a = "abc"会把对象放到常量池,String b = new String("abc")会在堆上创建新对象,所以a == b是false,a.equals(b)是true。面试官如果继续深入,还会问StringBuilder和StringBuffer的区别,核心是StringBuffer的方法加了synchronized所以线程安全但性能更差,而StringBuilder在单线程下更快。最后一个加分点是字符串拼接的底层优化,JDK 9之后字符串常量拼接在编译期会优化为常量,变量拼接会编译为invokedynamic调用StringConcatFactory的makeConcat方法,已经不完全是创建多个StringBuilder的字节码了。这块说清楚,基础分基本能拿稳。
2.2 HashMap的完整追问链与线程安全问题
HashMap是Java面试的绝对顶流,按追问链来准备最高效:
第一层数据结构:数组加链表,JDK 8以后加红黑树。
第二层为什么用红黑树:哈希冲突导致链表过长时查询退化为O(n),红黑树保证最坏O(logn),但节点比链表节点大,所以只在链表长度达到8且数组长度不小于64时才会树化。
第三层put流程:计算key的hash,这里要注意是key.hashCode()的高16位和低16位异或,目的是让高位参与数组下标计算;然后用(n - 1) & hash定位桶;如果桶为空直接newNode;如果桶不为空且key存在则覆盖;否则插入链表尾部;插入后判断是否达到树化阈值。
第四层扩容机制:默认负载因子0.75,当size超过threshold = capacity * loadFactor时扩容为原来的两倍。扩容后节点会重新分配,因为下标是(n - 1) & hash,n变成两倍后节点的位置要么不变,要么移动旧容量的偏移量。
第五层追问HashMap为什么线程不安全:并发put会丢元素,JDK 8之后死循环问题虽然通过尾插解决了,但数据覆盖和数据丢失依然存在;所以并发场景用ConcurrentHashMap,它的原理是数组加链表,并发控制用CAS和synchronized锁头节点,锁粒度比JDK 7的Segment方案更细。
这一套背熟后再去看源码,基本上考不到你了。
2.3 equals与hashCode的约定,以及异常机制
equals和hashCode是面试中一个特别容易踩的坑。核心约定只有一句:两个对象equals相等,hashCode必须相等;两个对象equals不等,hashCode可以相等但不应该相等。因为HashMap这类散列集合先比较hashCode定位桶,再靠equals确认是否同一个key。如果你重写了equals但没有重写hashCode,两个内容相同的对象会算出不同的hash值,明明逻辑上相等却放不进同一个桶,get的时候就会取不到值。另一个高频场景是重写equals时如何判断,不要直接用instanceof,因为子类也是父类的instance,严格写法是getClass()比较或者用反例设计,实际代码里最稳妥的是用Objects.equals判断每个关键字段。异常机制方面,要分清受检异常和非受检异常,受检异常必须被捕获或声明抛出,非受检异常包括RuntimeException及其子类,例如NullPointerException、ClassCastException。面试常问的还有try-with-resources,它要求资源实现AutoCloseable接口,能保证try块结束时自动调用close,避免手动关流遗漏。
3. 并发编程:JMM、synchronized、CAS与AQS
并发是整个Java面试的深水区,也是拉开候选人差距的核心板块。这个板块准备得越深,面试官对你好感度越高。我把最高频的问题浓缩成四条线。
3.1 JMM和volatile到底解决了什么问题
Java内存模型JMM定义了线程和主内存之间的抽象关系,规定所有变量存在主内存中,每个线程有自己的工作内存,线程只能操作工作内存再同步回主内存。这个设计带来的问题就是可见性和有序性。volatile可以保证可见性,它修饰的变量在写完后会立即刷回主内存,读的时候强制从主内存读;同时volatile写操作会插入内存屏障,防止指令重排序。经典例子是双检锁单例:不加volatile时,对象创建过程中的指令重排可能导致另一个线程拿到半初始化对象。为了讲清楚,可以现场画一个时间线:线程A执行instance = new Singleton()分解为分配内存、初始化对象、引用赋值三步;如果重排成分配内存、引用赋值、初始化对象,线程B在A还没完成初始化时就读到引用,拿到一个有引用但内部字段为空的关键对象。volatile禁用了这种重排,所以DCL里必须用volatile修饰。
需要注意的是volatile不保证原子性,比如count++这种复合操作依然线程不安全。这也是面试官最常见的引导式追问,你主动说出来反而显得真实掌握了。
3.2 synchronized的锁升级过程与Monitor机制
synchronized在JDK 6之后做了大量优化,核心是无锁到偏向锁、轻量级锁、重量级锁的升级路径。要能解释清楚每个状态:
- 无锁:没有竞争时,Mark Word记录无锁状态。
- 偏向锁:只有一个线程反复进入同步块,记录线程ID,避免每次加锁都做CAS。
- 轻量级锁:出现第二个线程竞争,撤销偏向锁,在栈帧中创建锁记录,通过CAS尝试将对象头的Mark Word替换为指向锁记录的指针。
- 重量级锁:CAS失败或自旋过多,膨胀为重量级锁,依赖操作系统互斥量实现阻塞和唤醒。
还要说清Monitor的含义,Monitor是管程模型,在Java中每个对象都可以成为Monitor,synchronized编译后通过monitorenter和monitorexit指令实现。面试官很喜欢问锁能不能降级,事实上偏向锁可以批量撤销,但锁升级后不会随便降级,重量级锁必须等锁释放。
3.3 CAS与AQS,ReentrantLock的设计骨架
CAS是Compare And Swap,底层是Unsafe类的compareAndSwapInt,通过CPU的原子指令保证比较并交换的原子性。CAS的优点是避免阻塞,缺点是会产生ABA问题。ABA的解决思路是加版本号或时间戳,Java里的AtomicStampedReference就是干这个的。CAS还有一个劣势是高竞争下自旋会消耗CPU。
AQS就是AbstractQueuedSynchronizer,它是整个java.util.concurrent锁机制的地基。维护一个volatile int state变量表示资源状态,加一个FIFO双向等待队列。ReentrantLock就基于AQS实现,state记录重入次数,加锁是CAS修改state,释放锁是state减一,减到0才真正释放。写锁和读锁的区别在于高16位和低16位分别记录写锁重入次数和读锁持有数量。AQS里的模板方法设计也是加分点,比如tryAcquire、tryRelease是留给子类实现的。记住一句话:AQS就是一套“获取资源失败就去排队,成功后唤醒队头线程”的框架。
3.4 线程池参数与拒绝策略不能只背数字
线程池问题看起来简单,但精确回答的人不多。核心是ThreadPoolExecutor的七个参数:核心线程数、最大线程数、空闲存活时间、时间单位、阻塞队列、线程工厂、拒绝策略。关键是“线程数怎么定”这个开放追问,没有标准答案,但要说出理由。CPU密集型任务建议设为核心数加一,IO密集型任务可以设为核心数乘以2,因为IO等待不占CPU。更严谨的做法是参考公式CPU核心数 / (1 - 阻塞系数)。面试官还喜欢问:线程池的执行流程。新任务来了,先判断核心线程是否满,没满直接创建线程执行;满了塞进队列;队列也满了,判断线程数是否达到最大线程数,没到就创建非核心线程;三种情况都满了走拒绝策略。
拒绝策略有四种:抛异常、用调用者线程执行、丢弃新任务、丢弃队列最老任务。实际开发中默认的AbortPolicy最安全,能快速暴露问题;而CallerRunsPolicy适合流量高峰,让调用线程帮助执行,相当于天然限流。这个点如果能结合你在项目里配过的参数来说,就非常出彩。
4. JVM:内存区域、垃圾回收与类加载机制
JVM题考察的是你能否理解Java程序运行时的底层行为。以前面试官只问堆和栈,现在会追到三色标记、GC日志分析、OOM排查这种实战级别。这部分我建议直接照着HotSpot源码思路来记。
4.1 运行时数据区:哪些区域会OOM
JVM运行时数据区分为程序计数器、虚拟机栈、本地方法栈、堆、方法区、运行时常量池,JDK 8之后字符串常量池挪到了堆中、运行时常量池挪到了本地内存的元空间。
虚拟机栈主要存栈帧,栈帧里有局部变量表、操作数栈、动态链接和返回地址。栈太深就会StackOverflowError。堆是GC主战场,存放所有对象实例;方法区在JDK 8后叫元空间,用本地内存实现,保存类元信息、静态变量、常量池等。真正高频的考点是各部分OOM的场景:堆空间不足是对象太多且存活,栈溢出是递归无限深度,元空间OOM是动态生成类太多,直接内存OOM是NIO的ByteBuffer分配太多。回答的时候最好带上jmap和jstat的排查命令,面试官会觉得你不仅懂理论还会排查。
4.2 垃圾回收:可达性分析、分代与三色标记
垃圾回收的第一步是判断对象是否存活,主流是可达性分析,从GC Roots出发,能到达的对象就是存活。GC Roots包括虚拟机栈中引用的对象、静态变量引用、常量引用、本地方法栈引用的对象等。然后按分代思路,对象创建在Eden区,Survivor区的对象经过Minior GC存活并年龄加一,超过15次进入老年代,这是-XX:MaxTenuringThreshold控制。大对象直接进老年代,动态年龄判定则会根据Survivor区占用情况提前晋升。
GC算法要掌握标记-清除、标记-复制、标记-整理。新生代用复制算法,因为存活对象少、复制成本低;老年代用标记-整理或标记-清除,因为对象存活率高。G1是目前比较主流的收集器,它是Region化的垃圾回收器,把堆分成多个Region,通过RSet维护跨Region引用。G1的Mixed GC会优先回收垃圾占比高的Region,预期停顿时间由-XX:MaxGCPauseMillis控制。CMS已经逐步退出主流,但G1和ZGC的区别仍然值得准备:ZGC的染色指针和读屏障让停顿时间几乎维持在毫秒级以下。至于三色标记,一定要能答出白色是未访问、灰色是扫描中、黑色是已扫描且引用已完成,并发标记阶段黑色对象引用白色对象时会用写屏障防止漏标。
4.3 类加载机制与双亲委派模型
类加载分成加载、验证、准备、解析、初始化五个阶段。准备阶段会为静态变量赋默认值,初始化阶段才执行静态代码块和赋值。类加载器分四层:启动类加载器、扩展类加载器、应用程序类加载器,以及自定义类加载器。双亲委派模型意思是,当需要加载一个类时,先让父加载器尝试加载,父加载器加载不到再由子加载器加载。好处是避免类被重复加载,也能保证核心类不会被篡改。
最常追问的问题是“能不能打破双亲委派模型,怎么打破”。当然能,例如JDBC的DriverManager使用SPI机制,在启动类加载器加载的类中需要调用应用程序类加载器加载的驱动实现,所以采用了线程上下文类加载器。真正写自定义类加载器时,重写findClass而不是loadClass,才算遵守双亲委派。如果面试官问的是热部署和Tomcat为什么打破双亲委派,要意识到Web容器需要每个应用有独立的类加载器,这样才能隔离不同版本依赖。
5. Spring与Spring Boot原理题:Bean、AOP、自动配置、事务
Spring全家桶是后端业务开发绕不开的框架。这块的面试题大多数从“原理是什么”和“失效场景有哪些”两个角度出发,准备时尤其要注意后者。
5.1 Bean生命周期与循环依赖
Bean的生命周期要能按阶段说:实例化、属性填充、初始化、使用、销毁。特殊节点包括BeanNameAware、BeanFactoryAware等Aware接口的调用,BeanPostProcessor在初始化前后执行,InitializingBean和@PostConstruct执行时机,以及DisposableBean和@PreDestroy。完整讲一遍面试官就满意了。
循环依赖更像一个隔离测试。Spring默认只处理单例Bean的setter循环依赖,靠三级缓存解决。一级缓存是完整单例池,二级缓存是提前暴露的早期对象,三级缓存存放的是ObjectFactory,它能通过getEarlyBeanReference生成代理对象。为什么需要三级而不是二级,关键在于保证代理对象在循环引用时也能正常注入。可以这么答:没有AOP时二级缓存够了,但AOP需要在合适时机生成代理,如果过早生成代理会破坏正常初始化流程,所以延迟到ObjectFactory里用SmartInstantiationAwareBeanPostProcessor决定是否替换为代理。构造器注入不能解决循环依赖,因为Bean在实例化阶段就卡死了,所以设计时尽量用setter或者@Lazy。
5.2 AOP的核心概念与代理选择
AOP的考点集中在切面、连接点、切点、通知、织入这几个概念。真正会深挖的是Spring AOP的实现方式:默认JDK动态代理,代理目标类必须实现接口;如果目标类没有接口,就使用CGLIB生成子类代理。Spring Boot 2.x之后默认开启CGLIB代理,即使接口存在,也使用CGLIB来避免类型转换问题。切点匹配的核心是表达式,例如execution(* com.example.service..*.*(..)),要能说明*号含义和不同通配符的规则。
事务本质上是AOP的一个具体应用。@Transactional通过TransactionInterceptor实现,在方法执行前开启事务,抛出RuntimeException回滚,正常返回提交。
5.3 Spring Boot自动配置到底是怎样发生的
自动配置的入口是@SpringBootApplication里的@EnableAutoConfiguration,它通过@Import导出AutoConfigurationImportSelector,selectImports方法会读取META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports文件中的配置类列表。每个自动配置类基本都由@ConditionalOnClass、@ConditionalOnMissingBean等条件注解控制,只有满足条件才生效。比如DataSourceAutoConfiguration,需要Classpath里有DataSource这个类才会绑定配置,最终生成一个数据源Bean。
心里要有一个完整的自问自答:如果用户自己定义了一个Bean,自动配置会不会覆盖?不会,因为条件注解里有@ConditionalOnMissingBean,判断不存在用户Bean时才装配默认Bean。优先级设计就是“用户定义优先,默认配置兜底”。
5.4 事务失效的几个隐藏陷阱
事务失效是面试高频,也是实际Bug高频。最经典的是同类内自调用,方法A调方法B,B上有@Transactional,但事务是通过代理对象拦截生效的,类内部this调用直接进入了原始对象,没有走代理切面,所以事务完全失效。解决方案是注入自身代理或把方法拆分到另一个Service里。
其他失效场景包括:方法或类不是public;异常被try-catch吞掉;抛出的是检查异常且没有配置rollbackFor;数据库存储引擎不支持事务如MyISAM;事务传播行为设置成NOT_SUPPORTED或 REQUIRES_NEW会挂起老事务。千万别只背结论,要结合代码说出场景里哪一步导致代理没有生效。
6. MySQL:索引、事务隔离、锁与慢SQL排查
数据库几乎是技术面试的另一半江山。你会发现JVM和并发答得好,MySQL这里口齿含糊,面试评价依然不稳。MySQL的问题非常体系化,建议按“索引怎么走、事务怎么隔离、锁怎么加、慢查询怎么优化”四条线来准备。
6.1 为什么索引选B+树:聚簇索引、二级索引与回表
B+树成为MySQL InnoDB默认索引结构的原因要答三点:矮胖树,磁盘IO次数少;叶子节点有双向指针,范围查询高效;所有数据都在叶子节点顺序存放,查询稳定。对比一下B树,B树的中间节点也存储数据,同样的数据量会让树更高,范围查询还需要遍历多个层级。哈希索引虽然等值查询更快,但不支持范围查询和排序。
聚簇索引的叶子节点直接保存整行数据,而二级索引的叶子节点保存的是主键值。因此通过二级索引查数据时要先找到主键,再回聚簇索引查完整行,这个过程叫回表。面试官追问“怎么避免回表”,答案是覆盖索引,索引里已经包含要查询的字段,就不用回表。而最左前缀法则要记清楚,复合索引(a, b, c)可以匹配a、a,b、a,b,c,但跳过a直接用b走不了索引。
6.2 事务隔离级别与MVCC实现
四个隔离级别分别是读未提交、读已提交、可重复读、串行化。默认是RR可重复读。要深入答MVCC,它依靠隐藏字段、Undo Log和ReadView实现。隐藏字段包括事务ID和回滚指针;ReadView记录了当前活跃事务列表,通过比较事务ID决定某个版本是否可见。
RR和RC的区别在于生成ReadView的时机:RC每次快照读都生成新的ReadView,所以能看到最新已提交数据;RR只在第一次快照读时生成ReadView,后续复用,因此同一个事务里两次查询结果一致。InnoDB RR下解决幻读除了靠MVCC快照读,当前读要依靠间隙锁和临键锁阻挡插入。串行化直接用锁强行串行。
6.3 行锁、间隙锁、临键锁与死锁排查思路
InnoDB锁的类型很多,按粒度有表锁和行锁,行锁分为共享锁和排他锁。最容易被忽略的是索引与锁的关系:如果更新语句没有走索引,行锁会升级为表锁,这在实际开发里是大坑。
RR隔离级别下,还有间隙锁锁住两个索引之间的间隙,以及Next-Key锁锁住当前记录和间隙。死锁的经典案例是交叉锁,事务A锁记录1再等记录2,事务B锁记录2再等记录1。排查死锁的第一步是看死锁日志,用SHOW ENGINE INNODB STATUS可以拿到最近的死锁信息,里面会明确写到两个事务各持有什么锁在等什么锁。优化方向是调整事务顺序,尽量一次锁住全部资源,缩短事务时间,降低锁冲突窗口。这里还要记住INNODB的死锁检测默认开启,会把代价更小的事务回滚。
6.4 慢查询SQL的优化三板斧
遇到一条慢SQL,我会按顺序排查:先explain看执行计划,重点看type、key、rows、Extra。type从好到坏依次是const、eq_ref、ref、range、index、all,看到了ALL基本就是全表扫描。key为NULL说明索引没生效。Extra若出现Using filesort,说明排序没走索引,出现Using temporary则说明有临时表。
优化的三个方向:第一,尽量让查询走索引,比如在WHERE条件列建索引,覆盖排序字段避免filesort。第二,避免写坏索引的SQL,不要在索引列上做函数运算、隐式类型转换、前导模糊查询。第三,避免一次性查太多列,把select *改成只查需要的字段,减少回表和网络传输。没有捷径,慢SQL分析就得多看执行计划,遇到问题先看是不是索引失效。
7. Redis必问考点:缓存三大问题、持久化与分布式锁
Redis是Java后端岗位简历上的常客。大多数人对基本操作很熟,但底层原理题和架构设计题容易乱。整理到三个足够高频的方向。
7.1 缓存穿透、击穿、雪崩的原理与应对
这三个问题从名字就能看出经典度,应对策略要背熟。
缓存穿透是查询一个不存在的key,请求直接打到数据库。方案有:把空值缓存并设置短过期时间;使用布隆过滤器,在缓存前加一层存在性判断。
缓存击穿是某个热点key过期瞬间,大量请求同时打到数据库。方案是:热点key不设置过期时间,或过期时间加随机值;使用互斥锁,只有第一个请求去数据库重建缓存,其他请求等待。
缓存雪崩是大批key在同一时间过期,或者Redis实例宕机,导致数据库压力暴涨。方案是:过期时间加随机抖振,避免同时失效;做多级缓存,本地缓存前置拦截;Redis集群高可用部署,比如主从加哨兵。
这里我特别建议准备一个自己能说通的“业务实例”,比如某个商品详情页接口,说明你是怎么定位热点数据、怎么设置过期时间的,比空泛背方案有效得多。
7.2 RDB和AOF要按数据安全维度来选
持久化两个方案各有利弊。RDB是定期生成二进制快照,文件小、恢复速度快,但两次快照之间的数据会丢失;AOF是记录每条写命令,默认everysec追加写,最多丢失1秒数据,数据安全性高,但文件体积大、恢复慢。优化的AOF方案还有混合持久化,先用RDB快照做全量,后续增量用AOF命令记录,兼顾了启动速度和数据安全。面试官常从两个角度追问:一是fork子进程时是否阻塞主进程,回答是fork瞬间阻塞,之后子进程写快照不阻塞;二是AOF rewrite机制,就是为了压缩历史命令,减少文件膨胀。
7.3 Redis分布式锁的演进与Redisson实现
最早版本的分布式锁是SETNX key value加过期时间,但容易出问题,后来改进为SET key value NX PX 30000一次性保证互斥与过期。可是锁过期后业务还没执行完怎么办?答案是看门狗续期机制。用Redisson,它的watch dog会在锁未释放时定期把过期时间续到30秒,保证业务不会因锁过期被其他线程抢入。
还要能回答“锁的可重入性”和“锁粒度控制”。例如同一个线程进入方法A锁住后,再调方法B也要同一把锁,Redisson的Hash结构记录线程ID和计数,实现重入。锁粒度则是尽量只锁住被修改的订单或库存ID,而不是锁整个表,这个点最容易体现架构思维。最后要说明分布式锁相比synchronized,解决的是多实例之间互斥的问题,单机锁在集群环境下失效,这是引入它的大前提。
8. 分布式与系统设计:CAP、分布式事务、限流方案
到中高级岗位,面试里会加入分布式世界观的考察。前几年问CAP是送分,现在会问“你的系统如何取舍”“分布式事务怎么落地”,这已经从记忆题变成了设计题。
8.1 CAP理论不能只背定义
CAP的核心是,一个分布式数据系统最多只能同时满足一致性、可用性、分区容错性中的两个。P是前提,网络分区在分布式环境里一定会发生,所以只能在C和A之间选择。典型取舍是:注册中心类场景可以容忍短暂不一致而优先选AP;金融账户类场景必须选CP,宁可短暂拒绝写请求也不能出现对账错乱。
但要注意,CAP里的“一致性”是线性一致性,非常强。实际工程里更多是BASE模型,基本可用加软状态加最终一致性。比如“先更新数据库,再删缓存”这种方案,如果不做补偿,缓存删除失败就会导致旧值长时间存在,所以要靠消息队列、定时任务或者BinLog监听来保证最终一致。
8.2 分布式事务的几种常见落地方案
分布式事务方案没有银弹,要按业务一致性要求选。经典的2PC两阶段提交通过prepare和commit两个阶段保证所有参与者原子提交,但缺点是协调者单点、同步阻塞、极端情况下参与者挂了无法Commit。TCC方案把事务拆成Try、Confirm、Cancel三个阶段,要求业务提供正向和反向操作,性能比2PC好但侵入性很强,很多业务逻辑很难拆出回滚动作。本地消息表方案在同一个本地事务里写业务表和消息表,然后通过定时任务把消息发送到消息队列,消费者成功处理后回调确认,实现最终一致性。基于消息队列的可靠性投递也类似,核心是“保存消息”和“发送消息”在同一事务内。
面试的时候别只背名词,一定要说清楚你这边的业务重试策略和幂等设计。分布式事务难点不只是协调,更多的是“消息不丢”和“重复消息不重复处理”的兜底。
8.3 限流算法:漏桶、令牌桶与滑动窗口对比
服务端抗流量,限流是必备手段。漏桶算法把请求像水一样注入漏斗,漏斗按固定速率漏出,优点是流量整形能力强,能削峰填谷;缺点是面对突发流量时不够灵活,桶满了会直接丢弃,即使此时系统还有余量。令牌桶算法则按固定速率往桶里放令牌,请求需要拿令牌才能通过,桶里有积攒令牌时可以应对突发流量,更适合大多数业务网关。滑动窗口比固定窗口更精细,固定窗口在窗口切换瞬间可能出现双倍流量,滑动窗口通过细分时间段来降低突刺。此外还有计数器算法和基于Redis的分布式限流。实际项目里常见组合是Guava RateLimiter做单机限流,Redis加Lua脚本做集群限流,用滑动窗口加令牌桶思想实现更平滑的控制。
面这部分题时,我给的建议是结合你真实的接口设计去说,比如“登录接口按用户维度限流、支付接口按全局维度限流”,远比空讲算法更能说明你会干活。
最后再分享一个我准备面试的实际习惯。八股文不要整篇拿着背,而是把它当“考点索引”,每道题都自己尝试用一段大白话解释给一个完全不懂Java的朋友听。解释不清楚的地方,就是你的知识空洞,回归源码或官方文档去补。真正到了面试现场,你脑海里存的不是标准答案,而是自己组织过的思路,面对追问才不会慌。2026年的Java面试只会更加看重你能不能把原理讲到业务场景里,请一定多做“原理结合实践”这一步。