Java大厂面试现场:面试官与水货程序员谢飞机的爆笑对线实录
面试官:请坐,先简单自我介绍一下?
谢飞机:面试官好,我叫谢飞机,三年 CRUD 经验,主打一个增删改查特别熟练,最近两年背了 200 道八股文,信心满满来冲大厂!
面试官(微微一笑):不错,那我们开始吧。
第一轮:HashMap 的底层实现原理是什么?
谢飞机:这个我会!HashMap 是基于哈希表实现的,用数组 + 链表 + 红黑树,JDK8 开始链表长度超过 8 就转成红黑树,防止哈希碰撞导致性能退化。而且它是非线程安全的,效率高!
面试官:嗯,回答得不错,那你知道 put 方法的具体流程吗?
谢飞机:当然!先算 hash 值,然后找桶位置,没冲突就直接放,有冲突就挂在后面,链表变长了就树化……大概就这样。
面试官:还行,至少基础掌握得可以。
第二轮:那如果多个线程同时 put,可能会出现什么问题?
谢飞机:呃……会……会打架?数据错乱?我记得以前版本会有死循环,因为头插法导致链表反转,多线程扩容时形成环……不过现在不是改了吗?
面试官:算是蒙对了一半。JDK7 确实头插法容易出环,JDK8 改为尾插法后避免了这个问题,但依然不保证线程安全,可能覆盖、丢失数据。
第三轮:那你说说 ConcurrentHashMap 是怎么实现线程安全的?
谢飞机:ConcurrentHashMap?那必须是加锁啊!每个桶都上锁,谁抢到谁写!
面试官:具体点?
谢飞机:呃……ReentrantLock?synchronized?我忘了……反正就是锁住一部分,不让别人写。
面试官:JDK8 用的是什么?
谢飞机:是……是 CAS + synchronized 吧?对!Node 数组用 volatile,头节点加 synchronized 控制写入,读操作完全无锁!
面试官:勉强及格。
第四轮:聊聊线程池的核心参数有哪些?
谢飞机:这个我会!corePoolSize、maximumPoolSize、workQueue、threadFactory、handler,还有 keepAliveTime!
面试官:很好,那如果队列满了,会发生什么?
谢飞机:那就新建线程,直到最大线程数,再满了就执行拒绝策略!
面试官:说说常见的拒绝策略?
谢飞机:AbortPolicy —— 直接抛异常;CallerRunsPolicy —— 调用者自己执行;DiscardOldestPolicy —— 扔掉最老的;DiscardPolicy —— 直接扔掉新来的。
面试官:不错,看来背得挺熟。
第五轮:Spring 中 Bean 的作用域有哪些?
谢飞机:singleton、prototype、request、session、application,还有 WebSocket 的 scope!
面试官:singleton 是怎么保证只有一个实例的?
谢飞机:Spring 容器内部有个 map,叫 singletonObjects,第一次创建就 put 进去,下次 get 直接拿,不用再创建!
面试官:不错,理解到位。
第六轮:Redis 如何实现分布式锁?
谢飞机:SET key value NX EX 60!原子操作,防止重复加锁,过期时间防止死锁!
面试官:如果业务执行时间超过过期时间呢?
谢飞机:呃……那就续命!搞个看门狗,每隔一段时间 renew 一下 TTL!
面试官:你指的是 Redisson 的 Watchdog 机制?
谢飞机:对对对!就是那个“叮咚”一直响的!
面试官:……
第七轮:MySQL 的索引失效场景有哪些?
谢飞机:左模糊查询 like '%xx',不对!是 '%xx' 不走索引,'xx%' 可以!还有函数操作、类型转换、OR 条件没都建索引、最左匹配原则破坏……
面试官:不错,基本都答到了。
第八轮:DDD 中的聚合根是什么?
谢飞机:呃……就是一群实体的头头?比如订单是聚合根,订单项归它管,外部只能通过订单来操作订单项!
面试官:那聚合根之间怎么通信?
谢飞机:发消息呗!事件驱动!OrderCreatedEvent,然后库存服务监听,扣减库存!
面试官:还行,有点概念。
最终轮:Docker 和 Kubernetes 的区别?
谢飞机:Docker 是造集装箱的,K8s 是管集装箱船的!一个负责打包应用,一个负责调度、扩缩容、自愈!
面试官(终于露出笑容):比喻得还挺形象。
面试官:今天的面试就到这里,你的基础还算扎实,虽然有些地方答得磕绊,但看得出来有准备。这样吧,你回去等通知吧,我们会尽快反馈。
谢飞机(松了一口气):好的好的,等您电话!
【附】各问题详细解析
1. HashMap 底层原理
- 数据结构:数组 + 链表(≤8)+ 红黑树(>8 且桶长度 ≥6)
- hash 计算:
(key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16),扰动函数减少碰撞 - put 流程:
- 计算 hash 值
- 根据 hash 找到桶位置
- 若为空则直接插入
- 否则遍历链表/红黑树,存在则替换,否则插入末尾
- 判断是否需要树化或扩容
- 扩容:2 倍扩容,rehash,JDK8 使用尾插法避免多线程死循环
2. ConcurrentHashMap 线程安全机制(JDK8)
- 使用
CAS + synchronized锁住链表头节点 - volatile 修饰 Node 数组,保证可见性
- sizeCtl 控制初始化和扩容状态
- 扩容时支持并发迁移(transfer)
- 提供并发度高的读操作(无锁)
3. 线程池核心参数
| 参数 | 说明 |
|---|---|
| corePoolSize | 核心线程数,常驻内存 |
| maximumPoolSize | 最大线程数 |
| workQueue | 阻塞队列,如 LinkedBlockingQueue |
| keepAliveTime | 非核心线程空闲存活时间 |
| threadFactory | 创建线程的工厂 |
| handler | 拒绝策略 |
4. Redis 分布式锁注意事项
- 必须原子性:SET key value NX EX seconds
- 可重入性:需记录线程标识 + 重入计数(Redisson 实现)
- 锁续期:Watchdog 机制,默认每 1/3 时间自动续约
- 防止误删:Lua 脚本校验 value 再删除
5. MySQL 索引失效场景
- 使用函数或表达式:
WHERE YEAR(create_time) = 2024 - 类型隐式转换:字符串字段传数字
- 左模糊:
LIKE '%xx' - OR 条件未全部命中索引
- 违反最左前缀原则(联合索引)
- 数据分布极偏(优化器认为全表更快)
6. DDD 聚合根
- 聚合:一组有内聚关系的实体和值对象
- 聚合根:聚合的根实体,唯一对外访问点
- 一致性边界:聚合内数据强一致,聚合间最终一致
- 通过领域事件实现跨聚合通信
本文由谢飞机亲历整理,如有雷同,纯属巧合。祝各位程序员兄弟早日上岸,远离‘等通知’魔咒!