宇信科技面试 --- Java问题复盘
这场面试几乎全是Java问题,正好暴露了你在传统后端方向上的一些真实水平。整体来看,你的表现不稳定 ------有的问题(技术框架、SAGA事务、Linux命令)回答得不错,但一到底层原理 和非常规方案就明显卡壳。下面逐一拆解。
一、分布式锁(MySQL实现方式)------ 没接触过 ≠ 可以不知道
你的回答
"用Redis来做分布式锁,结合MySQL做一个唯一索引起幂等作用。单独用MySQL做分布式锁,我这边没有遇到真实的开发场景。"
问题所在
面试官问"如果用MySQL做分布式锁怎么做",你回答"用Redis做"然后说自己没遇到过MySQL的场景。
这个回答的问题不是"不知道",而是你没有尝试用已有知识去推导,直接放弃了回答。
面试官为什么要问这个?因为分布式锁是高频考点 ,而MySQL实现是其中最简单的方案之一------它不需要引入Redis,纯数据库就能做。他不期望你实际用过,但期望你知道有这几种方案并说出思路。
正确答案
MySQL做分布式锁主要有三种方式:
| 方案 | 实现方式 | 优缺点 |
|---|---|---|
| 唯一索引 + INSERT | 建一张lock表,锁名设为唯一索引;获取锁时INSERT,释放时DELETE |
简单,但无超时机制,释放锁失败会死锁;需要心跳保活线程 |
| SELECT ... FOR UPDATE | 利用行锁,同一时刻只有一个线程能获取到锁 | 依赖事务,锁会随事务提交释放;有超时机制,但性能一般 |
| GET_LOCK() | MySQL内置的GET_LOCK(str, timeout)函数 |
纯数据库方案,支持超时,但只适用于同一连接;MySQL不同版本行为有差异,生产中使用较少 |
简单示例(方式一):
sql
-- 1. 建一张锁表
CREATE TABLE distributed_lock (
lock_key VARCHAR(64) PRIMARY KEY, -- 唯一索引
owner VARCHAR(64),
expire_time DATETIME
);
-- 2. 获取锁(INSERT成功即获得锁)
INSERT INTO distributed_lock (lock_key, owner, expire_time)
VALUES ('order_lock', 'instance_001', NOW() + INTERVAL 30 SECOND);
-- 如果INSERT失败(主键冲突),说明锁被占用
-- 3. 释放锁
DELETE FROM distributed_lock WHERE lock_key = 'order_lock' AND owner = 'instance_001';
-- 必须带上owner,避免释放了别人的锁
即使没实际做过,也可以这样说:
"我没在项目里用MySQL做过分布式锁,但我们用的是Redis + Redisson。但如果用MySQL实现的话,我知道可以用一张锁表加唯一索引,通过INSERT成功与否来判断锁获取结果;也可以用SELECT ... FOR UPDATE来实现排他锁。不过相比Redis,MySQL方案的性能要差一些,我们当时没选它。"
二、synchronized底层原理------回答太浅
你的回答
"它是一个独占锁、悲观锁,线程锁上之后有互斥性,直到线程执行完释放,别的线程才能获取。"
问题所在
这个回答只停留在表象层面,没有触及任何底层机制。面试官问"底层原理"时,期望你至少讲到:
- monitor(监视器) 的概念
- 对象头(Mark Word) 中的锁状态
- 锁升级(偏向锁 → 轻量级锁 → 重量级锁)
你说的"独占锁、悲观锁"是ReentrantLock也有的特征,没有说出synchronized的独特性。
正确答案
text
synchronized底层基于JVM的monitor(监视器)机制实现。
每个Java对象都有一个对象头(Mark Word),其中记录了锁的状态。
当线程进入synchronized代码块时,它会尝试获取该对象的monitor:
- 成功 → 进入代码块,monitor计数器+1(可重入)
- 失败 → 线程阻塞,等待锁释放
JDK 1.6之后引入了锁升级机制(偏向锁 → 轻量级锁 → 重量级锁):
- 偏向锁:只有一个线程访问时,Mark Word记录该线程ID,无需CAS操作
- 轻量级锁:多个线程交替访问时,使用CAS自旋尝试获取锁
- 重量级锁:竞争激烈时,升级为操作系统互斥量(Mutex),线程阻塞,涉及用户态/内核态切换
释放锁时,monitor计数器减为0,唤醒等待线程。
最低底线: 即使背不全,至少说出"基于对象头的Mark Word"、"有锁升级机制"、"偏向锁→轻量级锁→重量级锁"这三个关键词,面试官就知道你了解底层。
三、线程池资源管控------关键词不全
你的回答
"核心线程数、最大线程数、空闲时间、队列、拒绝策略。CPU密集型的线程数=CPU数+1。"
问题所在
回答的基本正确,但太简略了 。面试官问的是"资源管控涉及哪些因素",你只列了参数名,没有展开说明为什么这些参数重要、如何合理配置。
另外"CPU数+1"是常见公式,但你没说清楚:
- I/O密集型应该怎么配?
- 核心线程数和最大线程数之间的差值意味着什么?
补充回答
text
线程池资源管控主要涉及以下因素:
1. 核心参数配置(corePoolSize / maxPoolSize / keepAliveTime / workQueue / handler)
2. 线程数的估算:
- CPU密集型:核心线程数 = CPU核数 + 1(减少上下文切换)
- I/O密集型:核心线程数 = CPU核数 × 2(或更大,因为线程大部分时间在等待I/O)
- 混合型:可以用公式:线程数 = CPU核数 × (1 + 等待时间/计算时间)
3. 队列选择:
- 无界队列(LinkedBlockingQueue):可能导致OOM,不推荐
- 有界队列(ArrayBlockingQueue):控制资源消耗,配合拒绝策略
4. 拒绝策略:
- AbortPolicy(默认):抛异常
- CallerRunsPolicy:用调用者线程执行(削峰)
- DiscardPolicy / DiscardOldestPolicy:丢弃(谨慎使用)
5. 监控与动态调整:
- 通过线程池提供的监控方法(getActiveCount、getQueueSize)观察运行状态
- 配合Apollo/Nacos实现动态参数调整
四、熔断实现原理------知道组件但说不清原理
你的回答
"通过滑动窗口组件,检测慢调用比例和异常比例,超过比例就触发open模式。"
问题所在
提到了"滑动窗口"和"open模式",说明你知道Sentinel/Hystrix的概念,但没有把完整流程讲清楚,显得支离破碎。
正确答案
text
熔断的核心原理是:在固定时间窗口内统计请求的失败率,超过阈值则触发熔断,后续请求快速失败,避免级联故障。
以Sentinel为例,它使用滑动窗口(多个时间片组成一个窗口)来统计每个时间片内的请求数、异常数、响应时间等指标。
状态机:
┌─────────┐
│ CLOSED │ ------ 正常运行,统计失败率
│ (关闭) │
└────┬────┘
│ 失败率超过阈值
▼
┌─────────┐
│ OPEN │ ------ 熔断开启,所有请求直接快速失败
│ (开启) │
└────┬────┘
│ 等待sleepWindow时间后
▼
┌─────────┐
│HALF-OPEN│ ------ 放行少量请求探测,成功则恢复CLOSED,失败则回到OPEN
│(半开) │
└─────────┘
关键参数:
- 统计时间窗口(statIntervalMs):统计多久的数据
- 失败率阈值(failureRateThreshold):超过多少比例触发熔断
- 熔断恢复时间(sleepWindowMs):熔断后多久尝试恢复
提醒: 你答"open模式"时,如果能补上"CLOSED → OPEN → HALF-OPEN"这个状态机转换,面试官对你的评价会明显提升。
五、分布式锁(zk / etcd)------ 你完全没提,但面试官可能默认你知道
背景
虽然面试官没追问"除了Redis和MySQL还有没有其他方案",但如果他接着问,你很可能也答不上来。
补充知识
| 方案 | 实现方式 | 优点 | 缺点 |
|---|---|---|---|
| Redis(Redisson) | SET NX + 过期时间 + 看门狗 | 高性能、可自动续期 | 依赖Redis稳定性、主从切换可能丢锁 |
| ZooKeeper | 创建临时顺序节点,序号最小的获得锁 | 强一致性、支持Watch机制 | 性能不如Redis、需维护ZK集群 |
| etcd | Lease + Revision机制 | 强一致性、高性能 | 运维成本较高 |
| MySQL | 唯一索引 / SELECT FOR UPDATE | 简单,无需额外中间件 | 性能差、依赖数据库 |
面试场景模拟:
"除了Redis和MySQL,你还知道哪些分布式锁的实现方式?"
你可以这样答:
"还有ZooKeeper和etcd。ZooKeeper是利用临时顺序节点,序号最小的节点获得锁,配合Watch机制监听前一个节点的释放事件。etcd类似,利用Lease租约和Revision版本号来实现。这两种方案都提供强一致性,但性能不如Redis,一般用于对一致性要求极高的场景。"
如果你没实际用过,这段话至少证明你知道这些方案的存在和基本思路。
六、其他小问题
1. "核⼼线程数=CPU数+1"这个公式只适用于CPU密集型
你在回答中提到了这个公式,但没有说明前提条件 ------这个公式只适用于纯CPU密集型 任务。如果是I/O密集型 (比如数据库查询、HTTP调用、文件读写),线程数需要更大,通常用 CPU核数 × 2 或 CPU核数 × (1 + 等待时间/计算时间)。
2. 记账业务讲清楚了,但缺少"对账"环节
你在讲记账流程时说了MQ发送账务数据给总账模块,但没有提对账 。银行系统记账的核心目标之一是账务平衡(总账 = 各核心子系统账务之和)。如果面试官追问"你怎么确保账最终是平的?"你可能会卡住。
备一个回答:
"除了实时记账外,我们每天日切后会跑一个对账批处理,把贷款核心、存款核心的流水和总账做一次汇总比对,发现差额会生成对账差异报告,人工介入处理。"
七、关于出差和加班 ------ 你的表达可以更职业
你的回答
"周末加班有加班费吗?" → "没有。" → "那行吧。"
"出差补助的标准是多少呢?" → 追问了两次
问题所在
问薪资福利完全没问题,这是你的权利。但你的问法有点"直接在谈判桌上掀底牌"的感觉------"没有加班费,那行吧"这种语气容易让面试官觉得你比较计较。
建议的表达方式
"关于加班和出差,我这边可以接受。我之前在银行驻场也经历过上线冲刺,连续一两周加班到很晚的情况都有。只是我这边刚结婚,如果出差周期太长,希望中间能有回西安的机会。具体的补贴和调休政策,我后面再跟HR详细了解一下。"
核心差异:
- 你先表达接受 ,再提诉求 (回家的机会、调休),最后说细节问HR
- 这样既维护了自己的权益,又不会让面试官觉得你"还没入职就在讨价还价"
八、综合判断
通过概率
这场面试的难度明显低于前两场,面试官问的大多是标准八股题。你的表现中规中矩------该答的答上了(SAGA、技术框架、Linux命令),该露怯的也露了(synchronized底层、MySQL分布式锁、熔断原理不完整)。
好消息是:这些问题都可以短期突击解决。
本场暴露的核心短板(按严重程度)
| 排序 | 知识点 | 建议行动 |
|---|---|---|
| 1 | synchronized底层原理 | 背熟"对象头Mark Word → 锁升级(偏向→轻量→重量)→ monitor"这条线 |
| 2 | MySQL分布式锁的三种实现 | 能说出唯一索引+INSERT / FOR UPDATE / GET_LOCK即可应急 |
| 3 | 熔断原理(状态机) | 背熟CLOSED → OPEN → HALF-OPEN三态流转 |
| 4 | 线程池完整参数配置 | 把CPU密集/I/O密集的公式和队列选择逻辑记清楚 |
| 5 | 分布式锁方案全景(Redis/MySQL/ZK/etcd) | 知道每种方案的存在和适用场景,下次被追问不会措手不及 |
短期冲刺建议(3天内)
- synchronized底层:把对象头结构、锁升级过程讲一遍录下来,听到自己流利为止
- MySQL分布式锁:把建表SQL和获取/释放逻辑写在备忘录里,面试前看一遍
- 熔断三态:画一遍状态图,确保自己能顺着箭头讲完整个流程
Java基础部分只要这3个点补上,你下次再面Java岗位,体感会完全不同。