面灵AI→

靖安科技一面:Java 全链路 97 问与无人机平台深挖

轮次
一面
时间
2026-09
来源
牛客网

《面试题目》

  1. 请做自我介绍。
  2. 上一段实习为什么离职?
  3. 聊天列表去重后的用户编码,根据编码查用户信息,用什么集合?
  4. 多个请求同时修改一个 HashMap 会有什么问题?
  5. 数据库只剩一个商品,两个人同时下单,程序中查到还有库存怎么办?
  6. 这个锁你会怎么加?
  7. 创建订单接口:接口请求、业务判断、写数据库分别放在哪?
  8. 业务比较简单(二三十行),还放 service 还是放 controller?
  9. 你第一个学的语言是 Java 吗?
  10. == 和 equals 的区别?
  11. equals 和 hashCode?
  12. String 源码看过吗?
  13. String 是什么修饰的?
  14. 类能被重写吗?
  15. String 是线程安全的吗?
  16. StringBuffer 和 StringBuilder 的 append 都是线程安全的吗?
  17. synchronized 可重入吗?
  18. synchronized 的底层原理?
  19. 订单创建成功但积分扣失败,怎么避免订单和积分状态不一致?
  20. 加了 @Transactional 注解,什么情况下会失效?
  21. MySQL 索引 + EasyExcel 分批导出 + 线程池,讲讲?
  22. 为什么不用 Apache POI?
  23. 为什么 EasyExcel 这么快?
  24. 要导出几百 MB 的设备日志文件,你怎么设计导出方式?
  25. 商品价格存 MySQL + Redis 缓存,修改价格后怎么避免用户看到旧价?
  26. 5000 份文档的切片与向量化,讲讲?
  27. 切片策略是什么?
  28. 用的什么向量数据库?
  29. 为什么不用 ES 做语义检索?
  30. 58 → 92 这个指标是怎么来的?
  31. 保存 10 万条聊天记录,支持按位置读取、末尾添加删除,用什么数据结构?
  32. 根据消息 ID 查到消息再删除,用列表一定更快吗?
  33. HashMap 为什么很快?
  34. HashMap 怎么遍历?
  35. 1000 万个接口耗时已知,查最慢的 100 条,会全部排序吗(空间只给 100)?
  36. 最大堆 / 最小堆怎么实现?
  37. 运维知识库是 QA 助手还是 Agent?
  38. 一个问题从开始到结束,系统内部都做什么?
  39. 意图识别怎么做?
  40. 为什么用 WebSocket + Protobuf(二进制协议)?
  41. TCP 和 UDP 的区别?
  42. 文件传输、实时语音分别更看重什么?
  43. 什么是跨域?
  44. 只配置 CORS 能阻止别人脚本调用接口吗?
  45. Cookie、Session、JWT 分别是什么?
  46. Cookie 存在哪、什么时候发送?
  47. Cookie 的发送规则?
  48. JWT 里能放密码吗?
  49. 用户退出后,原来的 JWT 会自动失效吗?
  50. 每进来一个请求创建一个线程、处理完销毁,有什么问题?
  51. 怎么解决?
  52. 请求一直进来、处理速度跟不上怎么办?
  53. 有什么措施?
  54. 用过什么类似的工具(线程池)?
  55. 线程池有哪些参数?
  56. 拒绝策略实现在哪个类?
  57. 一个线程调远程接口等 3 秒返回,这 3 秒一直占 CPU 吗?
  58. 1000 个请求都在等待,加 CPU 核心有用吗?
  59. 正在计算和正在等待有什么区别?
  60. 一台机器 8 个 CPU,怎么能跑很多线程?
  61. 图片压缩和图片下载(CPU 密集 vs IO 密集)适合的线程数一样吗?
  62. final 关键字?
  63. final 修饰一个 List,能对它 add 吗?
  64. Redis 预减库存 + Lua 原子脚本防超卖 + RocketMQ,讲讲?
  65. Redis 有几种数据结构?
  66. RocketMQ 有什么持久化策略?
  67. MySQL 最左前缀法则?
  68. 给 ABC 建联合索引,只查 B、C 会用这个索引吗?
  69. 写一个 update 语句:改某个用户的电话。
  70. 前端点击超时后再次点击创建订单接口怎么办?
  71. 怎么避免重复提交?
  72. AI 回答用 SSE 还是 WebSocket,为什么?
  73. 后端 SSE 分段推、前端一次性全出来,什么原因?
  74. 用户点发送一直转圈,按什么顺序排查?
  75. 说一下你实际优化过的接口、业务用途?
  76. 3.2 秒主要卡在哪?
  77. 卡在数据库还是外部接口还是等待?
  78. 用什么工具判断瓶颈?
  79. Redis 里具体存了什么?
  80. 线程池异步具体在哪一步执行?
  81. 是多个查询一起并行执行吗?
  82. 600 毫秒是业务已经完成了吗(落库?)?
  83. 无人机平台的整体架构?
  84. 系统有哪些能力?
  85. 10 万架无人机是对象存在内存里?
  86. 每台设备多久更新一次?
  87. 发过来的消息带什么?
  88. MQTT 了解吗?
  89. 物模型有概念吗?
  90. Spring Boot 的 application 注解 / 配置文件加载过程?
  91. 仿真引擎的视野驱动推送怎么实现?
  92. 对角线(视野范围)怎么过滤?
  93. 全部遍历一遍吗(空间索引)?
  94. 无人机飞进视野了怎么办?
  95. 开 100 个前端会怎么样?
  96. 线性插值算轨迹、急转弯 / 加减速不合理轨迹怎么处理?
  97. 这是轨迹预测吗?

《参考解析》

订单与积分的一致性,以及 @Transactional 为什么会失效

跨表/跨服务的「订单成功但积分失败」本质是分布式一致性问题,先分清两种取舍:如果积分只是附属权益,就走最终一致——订单本地事务里同时写一条积分流水(或本地消息表),事务提交后由定时任务或 MQ 异步扣积分,失败重试 + 幂等(用业务唯一键去重),再加一个 T+1 对账任务捞出差额人工/自动补偿;如果必须强一致且都在同一个库,就把订单和积分放进同一个本地事务,用行锁或乐观锁版本号保证并发正确。真正要避免的是「先远程调积分服务、成功了再写订单」这种没有补偿的写法。

@Transactional 失效的典型场景:① 方法不是 public(Spring AOP 代理拦不到)或同类内部方法自调用(走的是 this,绕过了代理);② 异常被自己 catch 掉没抛出去,或抛的是受检异常而默认只回滚 RuntimeException/Error(需要 rollbackFor = Exception.class);③ 传播行为设成 REQUIRES_NEW/NOT_SUPPORTED 导致当前事务被挂起;④ 多数据源没配对应的事务管理器,或者事务管理器与实际操作的库不匹配;⑤ 存储引擎是 MyISAM 这类不支持事务的;⑥ 注解打在接口上而代理模式是 CGLIB,或者类没被 Spring 管理。另外要提醒一个常被忽略的点:事务里做远程调用/发 MQ,会拉长事务持有时间,正确做法是事务内只落库、事务提交后再发消息(TransactionSynchronization 或本地消息表)。

MySQL 与 Redis 的价格缓存怎么保持一致

先想清楚「不一致能容忍多久、错价的代价有多大」。可行的方案按代价从低到高:Cache Aside + 先更新数据库再删缓存(不是更新缓存),并给缓存加过期时间兜底;删除失败时用消息队列重试删除,或用 binlog 订阅(Canal)异步刷新,把「删缓存」变成一个可靠事件。之所以是删除而不是更新,是因为并发写时两个请求的更新顺序无法保证,容易出现旧值覆盖新值的长尾脏数据。

更稳的做法是给价格加版本号或时间戳:读取缓存时带上版本,写入时版本更高的才能覆盖,读到低版本就丢弃重载;对价格这类强敏感数据,可以在 Redis 里只放「价格版本 + 短 TTL」,真正的价格从 DB 读,用很小的读放大换正确性。若要求绝对实时,就让价格读走 DB(或只读从库)不走缓存,把缓存让给商品描述这类不敏感字段。面试官常追问「延迟双删」——它只能缩小不一致窗口,不能消除,所以正解还是「删除 + 版本/过期 + 变更事件」,而不是靠 sleep。

从「每请求一个线程」到线程池:参数、拒绝策略与线程数怎么定

每请求新建线程的问题:创建/销毁本身有开销(内核态登记、栈内存分配),线程数不可控会打爆内存和上下文切换,且没有排队机制——流量峰值时不是变慢而是直接雪崩。解法是线程池:复用线程、用队列做缓冲、用拒绝策略做背压。

参数上要讲清「提交任务后的顺序」:核心线程 → 入队 → 扩到最大线程 → 拒绝。队列用无界 LinkedBlockingQueue 会让最大线程数失效并有 OOM 风险,所以生产上要有界;拒绝策略里 AbortPolicy 直接抛异常(默认)、CallerRunsPolicy 让提交线程自己跑形成背压、DiscardPolicy/DiscardOldestPolicy 会静默丢任务,一般不用(丢任务至少要留日志)。线程数的经验值:CPU 密集型 ≈ 核数 + 1,IO 密集型 ≈ 核数 × (1 + 等待时间/计算时间),但公式只是起点,必须压测。这也能解释面试里的连环问:一个线程等远程接口 3 秒期间不占 CPU(线程被挂起在等待队列,处于 WAITING/TIMED_WAITING),所以 1000 个等待请求加 CPU 核数意义不大,真正该加的是线程数或改成异步/响应式;而 8 核能跑很多线程,也正是因为大部分线程在等待而不是在计算。

synchronized 的可重入与底层原理

可重入指的是同一线程已经持有某对象的锁,再进入该对象的另一个 synchronized 方法/代码块时不会被自己阻塞,实现上靠对象头里的锁记录计数:每次重入计数加一,退出时减一,减到零才真正释放。它的意义在于避免自死锁——比如 synchronized 方法里调用同类的另一个 synchronized 方法。

底层原理要分层次答:字节码层面,同步代码块编译成 monitorenter/monitorexit(异常路径也会有一对保证释放),同步方法用方法访问标志 ACC_SYNCHRONIZED;对象层面,锁信息记录在对象头的 Mark Word 里,JVM 为了降低开销做了锁升级——偏向锁(同一线程反复进入,只在 Mark Word 记线程 ID,JDK 15 后默认关闭并逐步废弃)→ 轻量级锁(CAS 把 Mark Word 指向栈上的锁记录,适合无竞争或短时间自旋)→ 重量级锁(竞争激烈时向操作系统申请 mutex,未抢到的线程进入等待队列,涉及用户态/内核态切换)。要点是:升级不可逆、锁的是对象而不是代码、静态 synchronized 方法锁的是 Class 对象、synchronized 与 wait/notify 必须配合同一把锁使用;面试常追问它和 ReentrantLock 的区别——后者可中断、可超时、可公平、支持多个条件队列,代价是要手动 unlock。

SSE 与 WebSocket 怎么选,以及「后端分段推、前端一次性出来」的原因

选型先看通信方向与需求:SSE 是服务器到客户端的单向流,基于普通 HTTP(Content-Type: text/event-stream)、自带断线重连(Last-Event-ID)和事件 ID,实现简单、天然穿过大多数代理和网关;WebSocket 是全双工,适合需要客户端持续上行低延迟数据的场景(协同编辑、实时语音、游戏),代价是要处理握手升级、心跳保活、连接管理和扩容。大模型对话的典型形态是「一次提问、服务端持续下行」,所以 SSE 更合适;需要双向交互或多路复用时才上 WebSocket。

「后端已经分段推、前端却一次性全出来」几乎都是缓冲造成的,按概率排查:① 反向代理/Nginx/CDN 开了 proxy_buffering 或响应压缩,把小块攒成大块再下发(对应关掉缓冲、禁用对 text/event-stream 的 gzip);② 应用层没关输出缓冲或框架把响应包成了 JSON 一次性返回(比如用了普通 @RequestMapping 返回对象,而不是 SseEmitter/Flux);③ 前端用 fetch 拿完整 text() 再解析,而不是 EventSource 或流式读取 response.body;④ 服务端虽然分了段,但每段之间没有真正 flush,或者业务在等模型全部生成完才吐。定位手段很直接:先 curl -N 看服务端是不是真的逐块下发,再逐层加上代理看在哪一层被攒住,最后查前端读取方式。