本轮 AI+Java中级开发面试题库(带答案)
【面试题】1. RAG系统中检索到的文档和用户问题语义不相关时,如何提升回答质量?
【标准答案】这是RAG的典型痛点——检索噪声。我会从三个层面优化:第一,检索前做Query改写,用LLM对用户问题做意图扩展或拆解子问题,让检索词更精准;第二,检索时引入混合检索,关键词检索(BM25)和向量检索取交集或RRF融合排序,避免纯向量相似度带来的语义漂移;第三,检索后加重排模块,用Cross-Encoder对候选文档和Query做精排,过滤掉相似度虚高但实际不相关的文档。工程上还有一个兜底策略:如果重排后最高分仍低于阈值,直接让LLM回答"未找到相关信息"而不是硬编答案,这比胡说八道强得多。
【面试题】2. Java中ReentrantLock和synchronized的区别是什么?什么场景该用ReentrantLock?
【标准答案】核心区别有四个:第一,ReentrantLock是API层面的锁(基于AQS),synchronized是JVM层面的关键字锁;第二,ReentrantLock支持公平锁(new ReentrantLock(true)),synchronized只有非公平;第三,ReentrantLock支持可中断获取锁(lockInterruptibly),synchronized不行;第四,ReentrantLock支持多条件变量(Condition),可以做精准唤醒,synchronized只有wait/notifyAll全量唤醒。使用场景:需要公平性保证、需要尝试获取锁(tryLock带超时)、需要可中断锁、需要多个等待队列做精准唤醒时,选ReentrantLock。简单互斥场景用synchronized更简洁,JDK6之后锁升级优化已经很快了,别过度设计。
【面试题】3. Spring AI中如何实现一个多轮对话的ChatClient?上下文管理怎么做?
【标准答案】Spring AI的ChatClient本身是无状态的,多轮上下文要自己管。核心是用ChatMemory维护对话历史。具体做法:注入ChatMemory(比如InMemoryChatMemory或持久化实现),调用ChatClient时通过.advisors(new MessageChatMemoryAdvisor(chatMemory))把历史消息自动注入Prompt。每轮对话后,ChatMemory自动存储用户消息和助手回复。生产环境建议用Redis或数据库持久化ChatMemory,避免重启丢失。注意要设历史窗口大小(比如最近10轮),避免Token超限。如果对话很长,还可以做摘要压缩——把早期对话总结成一段文字替代原始消息,减少Token消耗。
【面试题】4. JVM中新生代和老年代的GC策略分别是什么?什么对象会直接进入老年代?
【标准答案】新生代用复制算法(Eden + Survivor区),默认比例8:1:1。新对象先分配在Eden,Minor GC时存活对象复制到Survivor,年龄加1,达到阈值(默认15)晋升老年代。直接进老年代的有三种情况:第一,大对象(超过-XX:PretenureSizeThreshold设置的值)直接分配在老年代,避免在新生代来回复制;第二,长期存活对象年龄达标后晋升;第三,动态年龄判断——Survivor中相同年龄对象大小总和超过Survivor空间一半,该年龄及以上对象直接晋升。老年代用标记-整理或标记-清除算法,CMS用标记-清除会有碎片,Full GC时才整理。
【面试题】5. 向量数据库中HNSW索引的原理是什么?为什么查询速度快但构建慢?
【标准答案】HNSW是分层可导航小世界图索引,灵感来自六度分隔理论。它构建一个多层图结构:底层包含所有向量,越高层节点越稀疏。查询时从最高层入口节点开始,贪心搜索找到离目标最近的节点,然后逐层向下,每层缩小搜索范围,最终在底层精确定位。速度快的原因:多层结构实现了"跳表式"的跳跃搜索,不需要暴力扫描全量数据,时间复杂度接近O(logN)。构建慢的原因:每个向量插入时要逐层找最近邻、建连边,还要维护图的连通性和导航性,计算量是O(N·logN·M),M是每层的连接数。另外HNSW是纯内存索引,构建时要频繁做距离计算,所以大数据集构建很耗时。适合查询频繁、数据相对稳定的场景。
【面试题】6. Redis分布式锁用SETNX实现有什么问题?Redlock算法怎么解决的?
【标准答案】简单SETNX有三个致命问题:第一,锁过期但业务没执行完,其他客户端拿到锁导致并发冲突;第二,客户端持锁期间宕机,锁无法主动释放,只能等过期;第三,主从同步延迟,主节点加锁后未同步到从节点就宕机,从节点升主后锁丢失,其他客户端可重复加锁。Redlock的解决思路:在多个独立Redis实例(通常5个)上同时加锁,只有超过半数(N/2+1)加锁成功且总耗时没超过锁过期时间,才算加锁成功。释放锁时向所有实例发DEL。这样即使1-2个实例宕机,锁仍然安全。但Redlock也有争议——Martin Kleppmann指出它依赖系统时钟同步,时钟跳变可能导致锁提前过期。实际项目中更推荐用Redisson的看门狗机制:后台线程定期续期,业务没完成锁就不会过期,简单有效。
【面试题】7. 如何用LangChain实现一个带工具调用的AI Agent?核心流程是什么?
【标准答案】LangChain Agent的核心流程是ReAct循环:思考→行动→观察→再思考。具体实现:第一步,定义工具集,用@tool装饰器把Python函数注册为Agent可调用的工具,描述清楚功能签名;第二步,创建Agent,用create_react_agent把LLM、工具集、提示词模板组合起来;第三步,用AgentExecutor执行,用户输入后Agent自主决策调用哪些工具、传什么参数、拿到结果后继续推理还是直接回答。AgentExecutor会循环执行直到Agent输出最终答案或达到最大步数限制。关键点:工具描述要精确,LLM靠描述决定是否调用;设max_iterations防止死循环;复杂场景可以用LangGraph构建状态机,比ReAct更可控。生产环境务必加人工审批节点,防止Agent自主执行危险操作。
【面试题】8. MySQL中InnoDB的MVCC机制是如何实现的?RR隔离级别下能完全避免幻读吗?
【标准答案】MVCC靠隐藏列+Undo Log+ReadView实现。每行数据有两个隐藏列:trx_id(最近修改的事务ID)和roll_pointer(指向Undo Log的前版本链)。ReadView记录当前活跃事务ID列表,判断版本可见性的规则是:trx_id小于ReadView中最小活跃ID则可见,大于最大ID则不可见,在范围内看是否在活跃列表中。RR级别下,ReadView在事务第一次SELECT时创建并复用,所以同一事务内多次读结果一致。但RR不能完全避免幻读:快照读用MVCC没问题,但当前读(SELECT FOR UPDATE、UPDATE等)会读最新数据,如果别的事务插入了新行,当前读就能看到,这就是幻读。MySQL通过Next-Key Lock(行锁+间隙锁)在当前读时锁住范围来防止幻读,但这不是MVCC本身的能力。
【面试题】9. 大模型微调中LoRA的原理是什么?为什么它比全量微调更高效?
【标准答案】LoRA的核心思想:大模型权重矩阵W的微调变化量ΔW可以用低秩分解表示,ΔW = A×B,其中A是d×r的矩阵,B是r×d的矩阵,r远小于d。训练时冻结原始权重W,只训练A和B这两个小矩阵,推理时把ΔW合并回W即可,不增加推理延迟。高效的原因:第一,参数量大幅减少,比如7B模型全量微调要训练70亿参数,LoRA用r=8可能只需训练几百万参数,显存占用降90%以上;第二,不需要为全量权重保存优化器状态,训练速度更快;第三,多个LoRA适配器可以共享同一个基座模型,切换任务只需换适配器,部署成本极低。关键超参是秩r,通常8-64即可,过大反而容易过拟合。QLoRA进一步把基座模型4-bit量化,训练时反量化,再叠加LoRA,单卡就能微调65B模型。
【面试题】10. 分布式系统中如何保证接口的幂等性?列举三种方案并说明适用场景。
【标准答案】幂等性就是同一操作执行多次结果一致,分布式系统中网络重试、消息重复消费都可能导致重复执行。三种常见方案:第一,唯一Token机制——请求前先从服务端获取Token,提交时携带Token,服务端用Redis的SETNX校验并删除Token,重复请求因Token已删除而被拦截,适用于表单提交、支付等写操作;第二,数据库唯一索引——利用表的业务唯一键建唯一索引,重复插入会抛DuplicateKey异常,捕获后做幂等返回,适用于创建订单、注册等场景,简单粗暴但有效;第三,状态机判断——业务有明确状态流转(如订单:待支付→已支付→已发货),更新前先查状态,只有符合预期状态才允许变更,否则直接返回成功,适用于状态流转类业务。实际项目中三者经常组合使用,Token做入口拦截,唯一索引做兜底,状态机做业务层保障。