AI 面试相同请求为什么会重复调用,如何解决
AI 面试相同请求为什么会重复调用 AI用 ConcurrentHashMap CompletableFuture 实现 Single-flight一、先看我们遇到的问题在 AI 面试项目中用户提交答案后后端需要调用 AI 对答案进行评分。正常流程应该是提交答案 → 调用一次 AI → 返回评分结果但在真实环境中同一个逻辑请求可能在很短时间内到达多次。例如用户连续点击提交前端超时后自动重试网关重试两个线程几乎同时处理同一份答案。如果后端没有并发合并机制就会变成请求 A同一会话、同一题、同一答案→ 调用 AI 请求 B同一会话、同一题、同一答案→ 调用 AI 请求 C同一会话、同一题、同一答案→ 调用 AI结果是同一份答案被评分三次重复消耗模型 Token、线程和 AI 接口并发额度而且三次生成结果还可能略有不同。为了判断两个请求是不是同一次计算可以使用业务阶段 会话 ID 题号 答案摘要组成请求 Key。我们想要的效果是请求 A → 真正调用 AI ───────→ 返回 result 请求 B → 不再调用 AI → 等待 A → 返回同一个 result 请求 C → 不再调用 AI → 等待 A → 返回同一个 result这就是 Single-flight同一个 Key 同时到达时只让一个请求执行真实任务其他请求等待并共享它的结果。二、Single-flight 和 Redis 缓存有什么不同这里说的 Redis是指常见的“先读缓存未命中再计算并写回”的结果缓存用法。Stringresultredis.get(key);if(result!null){returnresult;}resultcallAi();redis.set(key,result);returnresult;如果 Redis 已经有结果后来的请求直接读取缓存确实不会重复调用 AI。问题发生在多个请求同时缓存未命中请求 A查询 Redis → miss → 调用 AI 请求 B查询 Redis → miss → 调用 AIA 调用 AI 时结果还没有写入 Redis。B 只知道“缓存中没有结果”并不知道“A 已经在计算”。因此普通结果缓存仍可能在首次缓存 miss 时重复调用 AI。Single-flight 解决的是另一个时间段的问题请求 A发现当前没人计算 → 成为 owner → 调用 AI 请求 B发现 A 正在计算 → 成为 follower → 等待 A可以这样记机制回答的问题复用的对象Redis 结果缓存这个结果以前算出来了吗已经完成的结果Single-flight这个结果现在有人在算吗正在进行的任务Redis 和 Single-flight 不是互相替代的关系它们可以组合使用。但本文只讨论 JVM 本地 Single-flight 的实现原理。三、Single-flight 是现成方法吗Single-flight 是一种并发设计模式不是 Java 标准库里一个名为SingleFlight的固定类。在 Java 中我们可以使用下面两个并发工具自己实现ConcurrentHashMap CompletableFuture它们分别解决两个问题ConcurrentHashMap相同 Key 的请求应该找到哪个任务 CompletableFuture任务结束后所有等待者怎样拿到同一个结果核心容器可以这样定义privatefinalConcurrentMapString,FlightEntryflightsnewConcurrentHashMap();Map 中保存的不是 AI 最终结果本身而是“一次正在执行或刚完成的 Flight”。每个FlightEntry内部可以包含一个CompletableFuture和过期时间。四、ConcurrentHashMap保证相同 Key 找到同一个任务1. 为什么不能使用普通 HashMap假设使用普通 Map并写成“先查询再创建”FlightEntryentryflights.get(key);if(entrynull){entrynewFlightEntry(newCompletableFuture());flights.put(key,entry);}两个线程可能同时执行线程 Aget(key) → null 线程 Bget(key) → null 线程 A创建 Future-A 线程 B创建 Future-B最终 A 和 B 各自认为自己是执行者仍然会调用两次 AI。问题不仅是普通HashMap线程不安全更重要的是“查询 判断 创建”这三个动作不是一个原子操作。2. 为什么使用 ConcurrentHashMap.compute可以通过compute原子地创建或复用 FlightAtomicBooleannewFlightnewAtomicBoolean(false);FlightEntryentryflights.compute(key,(ignored,existing)-{if(existingnull||existing.expireAtMillisnow){newFlight.set(true);returnnewFlightEntry(newCompletableFuture(),nowttlMillis);}returnexisting;});对于同一个 Keycompute中的这段更新逻辑会被原子协调。因此第一个进入的线程发现没有 Flight创建新 Entry后续线程发现 Entry 已存在直接复用只有创建 Entry 的线程把newFlight设置为true。执行结果如下线程 Acompute(k1) → 创建 Future-1 → newFlighttrue → owner 线程 Bcompute(k1) → 复用 Future-1 → newFlightfalse → follower 线程 Ccompute(k1) → 复用 Future-1 → newFlightfalse → follower3. AtomicBoolean 是做什么的compute返回的只是FlightEntry。无论创建还是复用三个线程最后都拿到同一个 Entry所以还要知道“这个 Entry 是不是我创建的”。newFlight就是当前线程的身份标记if(newFlight.get()){// owner真正执行 supplier}else{// follower等待 Future}这里使用AtomicBoolean是因为 Java Lambda 内不能修改普通局部布尔变量。它不是用来在多个请求之间共享 owner 状态每次调用execute都会创建自己的newFlight。4. 为什么不在 compute 里面调用 AIcompute只负责快速地创建或复用 Entry。耗时的 AI 调用放在compute外执行FlightEntryentryflights.compute(...);if(newFlight.get()){Tvaluesupplier.get();}这样 Map 的原子更新部分很短不会把漫长的网络请求放进 Map 的计算逻辑中。五、CompletableFuture让所有请求等待同一个结果1. 它在这里不是异步线程池很多人看到CompletableFuture会先想到异步执行但这里最重要的作用不是启动线程而是充当“将来才会有值的结果容器”。CompletableFutureObjectfuturenewCompletableFuture();这行代码不会自动创建线程也不会自动执行 AI。它只是创建了一个尚未完成的 Future初始状态未完成 owner 成功完成并保存结果 owner 失败异常完成并保存异常2. owner 如何发布成功结果只有newFlighttrue的 owner 执行真实任务Tvaluesupplier.get();entry.resultFuture.complete(value);returnvalue;supplier.get()在 AI 面试场景中就是那次昂贵的 AI 调用。当 owner 得到结果后调用complete(value)。同一个 Future 上正在等待的 follower 都会被唤醒并拿到这个 value。3. follower 如何等待follower 不执行自己的 Supplier而是等待 owner 对应的 FutureTreused(T)entry.resultFuture.get(waitTimeoutMillis,TimeUnit.MILLISECONDS);returnreused;所以即使 follower 传入了另一个 Supplier它也不会执行。它只关心 owner 最终发布的结果。这里使用带超时的get是为了避免 follower 因 owner 卡死而无限等待。需要注意follower 等待超时只代表“我不再等了”不会自动终止 owner 正在执行的 AI 调用。4. owner 失败时怎么办owner 调用 AI 可能抛出异常。这时不能只让 owner 自己失败否则 follower 会一直等待一个永远不会完成的 Future。这时需要让 Future 异常完成catch(Throwableex){entry.resultFuture.completeExceptionally(ex);flights.remove(key,entry);throwex;}completeExceptionally(ex)会唤醒所有 follower让它们知道这次共享任务失败了。remove(key, entry)是条件删除只有 Map 中仍然是这个 Entry 时才删除避免误删后来新建的 Flight。如果等待线程被中断还应该恢复线程的中断标记然后向上抛出异常。六、两个类组合后的完整流程假设 A、B、C 使用相同 Key 同时请求 AI 评分1. A 调用 compute(k1) Map 中没有 k1 A 创建 Future-1成为 owner 2. B 调用 compute(k1) Map 中已有 k1 → Future-1 B 成为 follower 3. C 调用 compute(k1) Map 中已有 k1 → Future-1 C 成为 follower 4. A 执行 supplier.get() 只有这里真正调用 AI 5. B、C 调用 Future-1.get(timeout) 它们等待不调用 AI 6. A 得到 result调用 Future-1.complete(result) 7. B、C 被唤醒也拿到 result两者的职责可以浓缩成一句话ConcurrentHashMap 让相同请求找到同一个任务CompletableFuture 让这些请求共享同一次执行的结果或异常。把上面的逻辑组合起来可以得到下面这段简化代码publicTTexecute(Stringkey,SupplierTsupplier){AtomicBooleannewFlightnewAtomicBoolean(false);FlightEntryentryflights.compute(key,(k,existing)-{if(existingnull||existing.expired()){newFlight.set(true);returnnewFlightEntry(newCompletableFuture(),expireAt);}returnexisting;});if(newFlight.get()){try{Tvaluesupplier.get();entry.resultFuture.complete(value);returnvalue;}catch(Throwableex){entry.resultFuture.completeExceptionally(ex);flights.remove(key,entry);throwex;}}return(T)entry.resultFuture.get(waitTimeout,MILLISECONDS);}七、可选的 TTL 和实现边界除了 FutureFlightEntry还可以带有expireAtMillis。这样成功完成的 Future 能在一个很短的 TTL 内保留稍晚到达的相同 Key 可以立即拿到已完成结果。这种设计除了合并“正在执行”的请求还提供了一个很短的结果复用窗口。过期 Entry 应当在同 Key 再次进入时被替换并通过定期清理避免 Map 持续增长。使用时还要注意三个边界Key 必须准确。Key 太粗会错误合并不同答案Key 太细则无法合并相同请求。等待超时不会取消 owner。follower 超时离开后旧 owner 可能仍在调用 AI。这个 Map 只存在于当前 JVM。它只能直接合并进入同一个应用实例的请求。八、总结AI 面试中的问题是相同评分请求同时到达导致后端重复调用 AI浪费 Token 和并发资源。普通 Redis 结果缓存复用的是“已经算完的结果”Single-flight 合并的是“现在正在进行的相同计算”。Java 中可以使用ConcurrentHashMap CompletableFuture构建本地 Single-flightConcurrentHashMap.compute() → 原子创建或复用同一个 Flight → 选出唯一 owner CompletableFuture → owner 发布结果或异常 → follower 等待并复用最终同一个 Key 即使同时到达多次也只有 owner 真正调用一次 AI其他请求只等待并共享这一次调用的结果。

相关新闻

五进制同步计数器设计:从状态机原理到工程实现

五进制同步计数器设计:从状态机原理到工程实现

1. 项目概述:从“二进制”到“五进制”的思维跃迁在数字电路的世界里,我们最熟悉的朋友莫过于二进制计数器了。从最简单的74LS161到复杂的可编程逻辑器件,二进制计数逻辑几乎无处不在,因为它天然契合了计算机“0”和“1”的底层语…

2026/8/6 17:04:14 阅读更多 →
赏金女王进阶技巧提高触发善用自旋转减少手动节奏避免紊乱

赏金女王进阶技巧提高触发善用自旋转减少手动节奏避免紊乱

赏金女王进阶技巧提高触发善用自旋转减少手动节奏避免紊乱PG电子游戏《赏金女王》是5轴3行、20条固定连线的视频游戏,核心机制在于级联消除倍数递增三选一免费旋转。掌握以下几点可以让你打出更高水平。每次符号爆炸消除,上方符号下落补位,形…

2026/8/6 17:03:14 阅读更多 →
你的Agent真的安全吗?说说Prompt注入之外的四大威胁

你的Agent真的安全吗?说说Prompt注入之外的四大威胁

本文整理自 QCon 北京 2026《Sunny Duan 基于 AI Native 的防御架构和实践》,通过 Ai好记 转录整理,以下为精炼整理后的内容。当软件越来越多以 Agent 的形态运行,安全这件事的玩法彻底变了。过去是防「别人攻进来」,现在是防「怎…

2026/8/6 17:03:13 阅读更多 →

最新新闻

嘉利恒兴交付交通银行和林格尔新区数据中心ECC控制中心——168万项目全记录

嘉利恒兴交付交通银行和林格尔新区数据中心ECC控制中心——168万项目全记录

导读: 2026年5月,交通银行和林格尔新区数据中心一期项目ECC(企业控制中心)家具采购项目正式招标,中标金额168万元。嘉利恒兴作为项目实际供应商,从方案设计、生产制造到安装交付,全程参与&#…

2026/8/6 17:44:36 阅读更多 →
百度网盘macOS效率工具:快速解锁SVIP体验与下载加速完整指南

百度网盘macOS效率工具:快速解锁SVIP体验与下载加速完整指南

百度网盘macOS效率工具:快速解锁SVIP体验与下载加速完整指南 【免费下载链接】BaiduNetdiskPlugin-macOS For macOS.百度网盘 破解SVIP、下载速度限制~ 项目地址: https://gitcode.com/gh_mirrors/ba/BaiduNetdiskPlugin-macOS 你是否曾经面对百度网盘缓慢的…

2026/8/6 17:44:36 阅读更多 →
DevExpress WinForms数据编辑器组件,提供丰富的数据输入样式!(一)

DevExpress WinForms数据编辑器组件,提供丰富的数据输入样式!(一)

DevExpress WinForms超过80个高影响力的WinForms编辑器和多用途控件,从屏蔽数据输入和内置数据验证到HTML格式化,DevExpress数据编辑库提供了无与伦比的数据编辑选项,包括用于独立数据编辑或用于容器控件(如Grid, TreeList和Ribbon)的单元格。…

2026/8/6 17:44:36 阅读更多 →
乌鲁木齐没考上高中技校选择分享

乌鲁木齐没考上高中技校选择分享

引言对于许多在乌鲁木齐未能如愿进入高中的学生来说,选择一所合适的技工学校成为了他们迈向成功的重要一步。面对众多的选项,如何做出明智的选择?本文将结合个人实践经验,分享关于在乌鲁木齐选择技校时的一些关键考虑因素&#xf…

2026/8/6 17:44:36 阅读更多 →
学生党必看!最新3款高评价英语词汇学习软件

学生党必看!最新3款高评价英语词汇学习软件

【摘要:】 本文结合我5年英语词汇领域的实操经验,实测3款市面上口碑靠前的词汇学习软件,从技术逻辑、实际留存效果、适配场景三个维度拆解,帮学生党避坑,不用瞎买课、做无效背诵,所有结论都有实测数据支撑&…

2026/8/6 17:44:36 阅读更多 →
界面组件DevExpress Reporting——支持图表本地化和可绑定属性

界面组件DevExpress Reporting——支持图表本地化和可绑定属性

DevExpress Reporting是.NET Framework下功能完善的报表平台,它附带了易于使用的Visual Studio报表设计器和丰富的报表控件集,包括数据透视表、图表,因此您可以构建无与伦比、信息清晰的报表。在最近的更新(v23.1)中,官方扩展了De…

2026/8/6 17:43:36 阅读更多 →

日新闻

深入解析LimboAI C++内核:架构设计与性能优化实战

深入解析LimboAI C++内核:架构设计与性能优化实战

1. 项目概述:为什么我们需要深入LimboAI的C内核?如果你是一名使用Godot引擎的游戏开发者,尤其是对AI行为逻辑有较高要求的项目,那么LimboAI这个名字你大概率不会陌生。它作为Godot 4生态中一个备受瞩目的行为树与状态机插件&#…

2026/8/6 0:00:06 阅读更多 →
Unity 2D游戏敌人AI系统:基于PlayMaker状态机与2D Toolkit的实战开发

Unity 2D游戏敌人AI系统:基于PlayMaker状态机与2D Toolkit的实战开发

1. 项目概述与核心思路大家好,我是老张,一个在游戏开发一线摸爬滚打了十多年的老码农。今天咱们接着聊《空洞骑士》风格2D动作游戏的Demo制作。上一期我们搭好了基础框架,处理了角色移动和碰撞,这一期,我们要让游戏世界…

2026/8/6 0:00:06 阅读更多 →
被动防火门市场前景发展趋势

被动防火门市场前景发展趋势

被动防火门依靠材质结构、密闭构造阻隔烟火蔓延,无需电控启动,是建筑被动消防系统核心构件,行业依托新规管控、城市更新、工业安全升级迎来稳定扩容,整体朝着合规化、专项化、低碳化、智能化方向发展。现阶段 GB12955‑2024 新版国…

2026/8/6 0:00:06 阅读更多 →

周新闻

最大流算法详解:从水管网络到Ford-Fulkerson与Dinic实战

最大流算法详解:从水管网络到Ford-Fulkerson与Dinic实战

1. 从水管网络到最大流:一个核心问题的诞生想象一下,你是一个城市供水系统的总工程师。你的城市有多个水源(水库),需要通过一个复杂的地下管道网络,将水输送到各个居民区。每条管道都有其最大通水能力&…

2026/8/5 15:00:43 阅读更多 →
基于Springboot的企业门户网站(源码+LW+调试文档+讲解)

基于Springboot的企业门户网站(源码+LW+调试文档+讲解)

温馨提示:本人主页置顶文章(点我)开头有 CSDN 平台官方提供的学长联系方式的名片! 温馨提示:本人主页置顶文章(点我)开头有 CSDN 平台官方提供的学长联系方式的名片! 温馨提示:本人主页置顶文章(点我)开头有 CSDN 平台…

2026/8/5 13:13:56 阅读更多 →
MATLAB xcorr函数详解:从互相关原理到四大实战应用

MATLAB xcorr函数详解:从互相关原理到四大实战应用

1. 从一次信号“找茬”说起:为什么我们需要互相关几年前,我在处理一组声学传感器数据时遇到了一个棘手的问题。我有两个麦克风记录了一段相同的音频信号,理论上它们接收到的声音波形应该非常相似,只是由于麦克风位置不同&#xff…

2026/8/5 10:20:36 阅读更多 →

月新闻

免费解锁百度网盘SVIP加速:macOS用户必备的下载提速终极指南

免费解锁百度网盘SVIP加速:macOS用户必备的下载提速终极指南

免费解锁百度网盘SVIP加速:macOS用户必备的下载提速终极指南 【免费下载链接】BaiduNetdiskPlugin-macOS For macOS.百度网盘 破解SVIP、下载速度限制~ 项目地址: https://gitcode.com/gh_mirrors/ba/BaiduNetdiskPlugin-macOS 还在为百度网盘macOS版的龟速下…

2026/8/5 23:28:39 阅读更多 →
终极ncmdump指南:3分钟实现网易云NCM音乐解密与格式转换

终极ncmdump指南:3分钟实现网易云NCM音乐解密与格式转换

终极ncmdump指南:3分钟实现网易云NCM音乐解密与格式转换 【免费下载链接】ncmdump 项目地址: https://gitcode.com/gh_mirrors/ncmd/ncmdump 还在为网易云音乐下载的NCM格式文件无法在其他播放器播放而烦恼吗?ncmdump解密工具帮你轻松解决这个困…

2026/8/5 21:00:14 阅读更多 →
HarmonyOS 应用开发《掌上英语》第81篇: 智能体卡片:为英语学习 App 打造桌面级学习助手

HarmonyOS 应用开发《掌上英语》第81篇: 智能体卡片:为英语学习 App 打造桌面级学习助手

AgentCard 智能体卡片:为英语学习 App 打造桌面级学习助手适用平台:HarmonyOS 7.0 (API 26 Beta)一、引言 HarmonyOS 7.0(API 26 Beta)新增了 AgentCard 智能体卡片能力,这是继 HMAF(鸿蒙智能体框架&#x…

2026/8/5 23:46:51 阅读更多 →