用友金融Java后端实习一面:HashMap与MQ幂等
- 轮次
- 一面
- 时间
- 2026-09
- 来源
- 牛客网
《面试题目》
- 实习项目拷打(响应时间是怎么做的优化的)
- HashMap 在 JDK 1.8 之后的升级
- HashMap 是线程安全的吗?为什么?
- 在并发场景之下使用什么?ConcurrentHashMap 为什么能够保证线程安全?
- 在实际开发场景之下有没有碰到过 OOM,OOM 主要是由哪些原因造成的?
- JVM 的内存区域构成
- 怎么保证 RabbitMQ 中的消息不重复消费,怎么保证 MQ 中的消息不丢失?
- 排查慢 SQL
- MySQL 怎么保证事务一致性?
- 开发过程中用得比较多的 AI 工具是什么?具体说说是怎么用的
- 有没有听过 skill,开发过程中有没有使用过 skill?
- 你对用友集团的了解
- 反问
《参考解析》
HashMap 在 JDK 1.8 的升级:底层结构从「数组 + 链表」变成「数组 + 链表 + 红黑树」,当某个桶的链表长度达到 8 且数组容量 ≥ 64 时转成红黑树,退化阈值为 6,把最坏情况下的查询复杂度从 O(n) 降到 O(log n)。插入方式从 1.7 的头插改成尾插,头插在并发扩容时会导致链表成环、进而 CPU 打满,尾插消除了这个问题(但 HashMap 依然不是线程安全的)。哈希计算从 1.7 的「4 次移位异或」简化成 h ^ (h >>> 16),高位参与运算以降低碰撞。扩容机制上,容量始终是 2 的幂(保证 hash & (n-1) 等价取模),1.8 扩容时不需要重新计算 hash,而是用 hash & oldCap 判断元素是留在原位置还是移动到 原位置 + oldCap,把 rehash 拆成两条链表一次遍历完成。默认初始容量 16、负载因子 0.75,树化阈值 8 是泊松分布下极低概率的折中。
HashMap 线程不安全与 ConcurrentHashMap:不安全体现在三处:多线程 put 可能互相覆盖(两个线程同时算出同一个桶,后写的覆盖前者);并发扩容时可能丢数据或读到中间态;1.7 的头插还可能成环。所以并发场景用 ConcurrentHashMap。1.8 的实现放弃了分段锁,改用 CAS + synchronized 锁单个桶头节点:数组为空时用 sizeCtl 做 CAS 初始化;插入空桶用 CAS 直接放;桶非空就 synchronized (f) 锁住头节点再操作链表/红黑树,锁粒度是单个桶,并发度等于桶数量。扩容支持多线程协同(transfer + ForwardingNode 标记,其他线程遇到正在迁移的桶会去帮忙),size 用 baseCount + CounterCell[] 分散计数避免热点。读操作完全无锁,靠 volatile 保证节点可见性(Node 的 val 和 next 是 volatile,1.8 后不保证强一致但保证安全)。要补一句:ConcurrentHashMap 只保证单次操作原子,get 后 put 的复合操作(比如先判断再写入)仍需 computeIfAbsent/putIfAbsent 或额外同步。
OOM 的常见原因:一是堆内存不足(Java heap space)——内存泄漏(静态集合只增不减、监听器未注销、ThreadLocal 未 remove、连接/流未关闭)或瞬时大对象/大数据量查询(一次 select * 拉几百万行、大文件一次性读入、超大数组);二是元空间溢出(Metaspace)——动态生成类过多(CGLIB 代理、热部署、Groovy/脚本引擎反复编译);三是线程过多(unable to create new native thread)——线程池无界创建、每次请求 new Thread;四是 GC 开销超限(GC overhead limit exceeded)——堆几乎耗尽、GC 反复却回收不出空间,本质仍是内存泄漏或堆太小;五是直接内存(Direct buffer memory)——NIO ByteBuf 未释放。排查套路固定:启动加 -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=...,出问题后抓 dump 用 MAT 看支配树和 GC Roots 引用链,定位到具体持有者;同时看 GC 日志区分是泄漏还是容量不足。
JVM 内存区域:线程私有的三块——程序计数器(当前字节码行号,唯一不会 OOM 的区域)、虚拟机栈(每个方法一个栈帧,存局部变量表、操作数栈、动态链接、返回地址;栈深度超限抛 StackOverflowError)、本地方法栈(Native 方法)。线程共享的两块——堆(对象实例与数组,分新生代 Eden + 两个 Survivor 和老年代,是 GC 主战场)、方法区(1.8 起由元空间实现,存在本地内存,存类元信息、运行时常量池、静态变量)。此外还有直接内存(NIO 的 DirectByteBuffer 走本地内存,不受 -Xmx 限制但受物理内存限制)。要能说出各区域对应的异常:堆 OOM、栈 StackOverflowError、元空间 OOM、直接内存 OOM。
RabbitMQ 不重复消费与不丢失:先说清 MQ 的默认语义是「至少一次」,所以不重复消费靠消费端幂等,不靠 MQ。幂等实现有四种常用手段:业务唯一键 + 数据库唯一索引(插入冲突即视为已处理)、去重表/Redis SETNX 记录消息 ID 并设 TTL、状态机前置校验(只有处于待处理状态才允许流转)、以及乐观锁版本号。注意 ack 时机决定重复范围:处理完业务再 ack 最多重复一次,先 ack 后处理则可能丢消息,通常选前者。不丢失要分三段保:生产端用 publisher confirm(异步确认 + 失败重发)和 mandatory/return 回调确认路由成功,必要时配合本地消息表保证「业务成功则消息必发」;broker 端声明 durable 队列 + persistent 消息,并用镜像队列或 quorum 队列防单点故障;消费端用手动 ack,业务异常时 nack 重入队(要防死循环,配重试次数与退避),超过次数投递到死信队列(DLX)并告警人工介入。
慢 SQL 排查:开 slow_query_log,用 long_query_time 圈定阈值,用 pt-query-digest 按总耗时排序找最值得优化的语句;拿到语句先 EXPLAIN(或 EXPLAIN ANALYZE)看 type、key、rows、Extra,重点识别全表扫描 ALL、Using filesort、Using temporary、以及预估行数与实际差异大导致的索引选错。常见整改方向:按最左前缀建联合索引并尽量做成覆盖索引;避免对索引列做函数/运算和隐式类型转换(字符串列传数字、LIKE '%x'、OR 跨列);大偏移分页改游标或延迟关联;子查询改 JOIN;SELECT 收敛字段避免回表;把统计类查询异步化或搬到离线/缓存。改完要回归执行计划与 Handler_read_*,并评估新增索引带来的写放大。
MySQL 怎么保证事务一致性:靠「日志 + 锁 + 隔离级别」三件套。原子性和持久性由 redo log 保证:修改先写 redo(WAL),事务提交时 innodb_flush_log_at_trx_commit=1 才刷盘,崩溃后按 redo 前滚恢复已提交事务。一致性由 undo log 保证:回滚时按 undo 反向恢复,同时 undo 还承担 MVCC 的历史版本(配合 ReadView 实现一致性快照读)。隔离性由锁和 MVCC 保证:行锁、间隙锁/Next-Key Lock 防幻读、RR 下快照读不加锁。两阶段提交解决 redo 与 binlog 的一致性(prepare → 写 binlog → commit),保证主从复制和崩溃恢复的数据一致。答完要补实践注意点:事务要短小、避免大事务(锁持有久、undo 膨胀、主从延迟大)、避免在事务里做远程调用、RR 与 RC 的选择要结合业务(RC 减少间隙锁、并发更好,但需要 binlog 用 row 格式)。
AI 工具与 Skill:如实回答你的使用方式,重点讲工作流而不是工具名。可以分三类:写代码(样板逻辑、单测、SQL 改写、正则、脚本)、理解代码(读老项目、解释报错栈、生成文档与注释)、以及提效(生成测试数据、写 commit message、做 code review 初筛)。同时给边界:核心业务逻辑、并发与事务、SQL 与鉴权自己复核;不把公司和客户代码贴到不合规的公有工具上。如果被问到 Skill,可以理解为把重复的团队流程封装成可复用的提示词/工具组合(比如固定格式的代码审查、固定模板的单测生成),并说明你用过或自己写过哪一类,以及它带来的实际收益(省了多少时间、减少了多少遗漏)。
对用友的了解:这类问题要提前做功课,落到具体业务而不是泛泛夸。可用的事实框架:用友是国内老牌的企业管理软件与云服务厂商,核心产品线覆盖大型企业的 YonBIP 商业创新平台、成长型企业的 YonSuite、以及面向中小企业的畅捷通 T+ 等,主打财务、人力、供应链、采购、制造的数字化。用友金融是面向金融行业的子公司/业务板块,做的是银行、保险、证券等金融机构的管理与业务系统。回答时再结合自己的经历说一句「我在实习里做的是 XX,和贵司的 XX 方向比较对口」,比背公司简介有说服力。
反问:实习岗适合问「实习生具体参与哪些模块、有没有导师带、能不能接触到真实线上系统」以及「转正机制和时间点」。技术面也可以问团队的技术栈(是自研框架还是主流 Spring 生态)、以及实习生的工作范围是否会涉及加班较多的交付期。