HashMap
第一轮
🤖 AI面试官:
说一下 HashMap 的底层实现原理?JDK1.7 和 1.8 有什么区别?为什么要做这些改动?
👤 本人回答:
HashMap的底层是数组 具体而言是结合了链表+红黑树实现的,对于jdk1.7和1.8 主要的区别就是1.8引入了红黑树,这样的话对于他的解决冲突和扩容进行一些优化
🧐复盘:
👉 优点: 知道“数组 + 链表 + 红黑树”
知道 1.8 引入红黑树
👉 问题(重点):
❌ 没讲清结构(数组里到底存什么?)
❌ 没讲冲突怎么解决(只说“优化”太空)
❌ 完全没提 hash、index计算
❌ 没讲 1.7 vs 1.8 核心差异(头插 vs 尾插)
❌ 没讲 resize
❌ 没讲 树化条件
❌ “为什么优化”没说出来(这是加分点)
第二轮
🤖 AI面试官:
HashMap 是怎么根据 key 找到数组下标的?hash 是怎么处理的?
👤 本人回答:
先计算key的hashCode,然后通过(table.length-1)&hash 计算位置,对于hash 是怎么处理的 我不太理解这个问题
🧐复盘:
👉 你说对的部分:
✔️ hashCode() ✔️
✔️ (n - 1) & hash ✔️(这个很关键!不少人不会)
👉 不足的地方:
❌ 没说 为什么要 & 运算(而不是 %)
❌ 没说 hash 是怎么“处理”的(扰动函数)
❌ 没说 为什么要减少哈希冲突
第三轮
🤖 AI面试官:
什么时候链表会变成红黑树?为什么是这个阈值?
👤 本人回答:
链表长度大于8并且数组长度大于等于64
🧐复盘:
👉 你现在的回答:
链表长度 > 8 且数组长度 ≥ 64
👉 问题在于:
❌ 只报结论,没有解释
❌ 没说“为什么是8”
❌ 没说“为什么要加64这个限制”
❌ 没说“如果不满足会发生什么”
总结
HashMap 底层是基于数组实现的哈希表结构,每个数组位置是一个桶,当发生哈希冲突时会使用链表或红黑树来存储数据。在插入元素时,首先通过 key 的 hashCode 计算出 hash 值,并通过扰动函数让高位参与运算以减少冲突,然后通过 (n - 1) & hash 计算数组下标,因为数组长度是 2 的幂,这样可以用位运算代替取模提升性能。如果该位置没有元素就直接插入,否则会判断 key 是否相同,相同则覆盖,不同则插入到链表中,在 JDK1.8 中当链表长度超过 8 且数组容量大于等于 64 时会转为红黑树以提升查询性能。在扩容方面,当元素数量超过容量乘以负载因子(默认0.75)时会进行 resize,扩容为原来的两倍。JDK1.7 和 1.8 的主要区别在于 1.8 引入了红黑树,并且将链表插入方式由头插法改为尾插法,从而避免了在多线程扩容时可能出现的链表成环死循环问题,不过 HashMap 本身仍然是非线程安全的,在并发场景下通常会使用 ConcurrentHashMap 来替代。
synchronized 和 ReentrantLock
第一轮
🤖 AI面试官:
synchronized 和 ReentrantLock 有什么区别?你在项目中怎么选择?
👤 本人回答:
synchronized 和 ReentrantLock都是用来进行加锁的 synchronized用于比较简单的业务 是隐式锁ReentrantLock用于比较复杂的业务是显式锁
🧐复盘:
👉优点: 知道都是锁
知道一个隐式,一个显式(不错的切入点)
👉 主要问题(比较关键):
太抽象,没有技术细节
什么叫“复杂”?面试官不会认可这种说法
没有对比核心能力(重点缺失)
可中断 ❌
公平锁 ❌
tryLock ❌
Condition ❌
没有“项目场景”
你说“复杂业务”,但没举例 → 等于没说
没有结论导向
第二轮
🤖 AI面试官:
ReentrantLock 相比 synchronized,多了哪些能力?
👤 本人回答:
支持公平锁和非公平锁、支持自定义中断、可以自行释放
🧐复盘:
👉 优点:
✔️ 提到了公平锁 / 非公平锁(这是关键点)
✔️ 知道“可中断”这个能力(方向是对的)
✔️ 知道需要“手动释放锁”(显式锁的特点)
👉 缺点:
❌ “自定义中断”表述不准确 → 应该说:支持可中断获取锁(lockInterruptibly)
❌ “自行释放”不算优势点 → 这是特性,但不是“比 synchronized 强”的能力
❌ 漏掉两个高频加分点: tryLock()(非阻塞获取锁) Condition(替代 wait/notify,更灵活)
❌ 没有解释“这些能力解决什么问题” → 面试官更想听的是:为什么要这些功能?
第三轮
🤖 AI面试官:
你刚提到公平锁,那公平锁和非公平锁有什么区别?为什么默认用非公平锁?
👤 本人回答:
公平锁按照线程等待的先后顺序获取锁,非公平锁允许插队,为了节省开销 所以默认使用公平锁
🧐复盘:
👉 优点:
✔️ 说清了公平锁是“按等待顺序获取锁”
✔️ 知道非公平锁允许“插队”
✔️ 意识到和“性能/开销”有关(方向是对的)
👉 缺点:
❌ 结论说反了(这是关键错误) → 默认是:非公平锁,不是公平锁
❌ “节省开销”说得太模糊 → 面试官更想听:为什么非公平锁吞吐量更高
❌ 没说公平锁的缺点 → 上下文不完整(像只记了一半)
第四轮
🤖 AI面试官:
在你的项目中,什么时候会用 synchronized,什么时候会用 ReentrantLock?请举一个具体场景。
👤 本人回答:
比如基础信息修改等不怎么频繁 条件比较少的场景下 使用synchronized,比如订单核销等条件比较多的情况下使用ReentrantLock
🧐复盘:
👉 优点:
- ✔️ 知道要用“业务场景”来区分(方向是对的)
- ✔️ 能举出例子(基础信息修改 / 订单核销),比纯概念强
- ✔️ 有“复杂度不同选不同锁”的意识
👉 缺点(这一题的关键问题):
- ❌ “条件多/少”这个标准不准确
→ 锁的选择本质不是看业务复杂度,而是看并发控制需求 - ❌ 场景不具体
→ “订单核销条件多”这句话没有并发细节(面试官会觉得你在泛说) - ❌ 没说出 ReentrantLock 真正用武之地:
- tryLock(防止阻塞)
- 可中断(避免死等)
- 多条件队列(Condition)
- ❌ 没体现“为什么不能用 synchronized”
→ 这是拉开差距的关键点
总结
synchronized 和 ReentrantLock 都是实现线程同步的方式,synchronized 是 JVM 层面的内置锁,使用简单,会自动释放锁,而 ReentrantLock 是基于 AQS 实现的显式锁,需要手动加锁和释放。相比之下,ReentrantLock 提供了更多灵活的功能,比如支持公平锁和非公平锁、支持可中断获取锁、支持 tryLock 尝试获取锁避免阻塞,以及通过 Condition 实现更灵活的线程通信。在实际项目中,如果是简单的同步场景或者并发不高的情况,我一般会使用 synchronized;而在需要更高并发控制能力的场景下,比如需要避免线程长时间阻塞、支持中断或者实现复杂的线程调度时,会选择 ReentrantLock,比如在订单处理或库存扣减中,可以通过 tryLock 实现快速失败或降级处理,提高系统的可用性。
Spring Bean
第一轮
🤖 AI面试官:
Spring Bean 的生命周期是怎样的?
👤 本人回答:
实例化 → 属性填充 → 初始化 → 销毁
🧐复盘:
👉 优点:
- ✔️ 知道核心四步(实例化 → 填充 → 初始化 → 销毁)
- ✔️ 没有说错方向(这一点很重要)
👉 缺点:
- ❌ 过于“骨架化”,缺少关键细节(面试官会觉得你只背了标题)
- ❌ 没提 Spring 的核心扩展点(这一题最重要的部分)
- ❌ 没提:
- Aware 接口
- BeanPostProcessor
- init-method / destroy-method
- ❌ 没体现“Spring 为什么强”(IOC 扩展能力)
第二轮
🤖 AI面试官:
在 Bean 初始化前后,Spring 是如何让开发者“介入”的?
👤 本人回答:
通过BeanPostProcessor拦截所有 Bean 的初始化前后,再配合@PostConstruct、InitializingBean等单 Bean 初始化接口,实现开发者自定义介入。
🧐复盘:
👉 优点:
- ✔️ 提到了 BeanPostProcessor(这一题核心命门)
- ✔️ 知道“初始化前后拦截”(说明理解流程,而不是只背步骤)
- ✔️ 能补充
@PostConstruct、InitializingBean(有扩展意识)
👉 缺点:
- ❌ 表达有一点“混层级”
→ BeanPostProcessor(全局扩展) vs InitializingBean(单 Bean 生命周期),最好区分清楚 - ❌ 少了两个关键方法名(面试加分点):
postProcessBeforeInitializationpostProcessAfterInitialization
- ❌ 没点出一个核心价值:
👉 AOP、代理对象就是在这里产生的(非常加分) - ❌ 没提 Aware 接口(常见追问点)
第四轮
🤖 AI面试官:
AOP 代理对象是在 Bean 生命周期的哪个阶段生成的?为什么是在这个阶段?
👤 本人回答:
Bean 初始化完成之后,不知道为什么
🧐复盘:
👉 优点:
- ✔️ 知道是在初始化之后(关键结论是对的)
- ✔️ 没乱编(这一点比很多人强)
👉 缺点:
- ❌ 不知道“为什么”(这一题真正的考点)
- ❌ 没说具体是在 哪个扩展点 做的(BeanPostProcessor)
- ❌ 没理解一个核心点:
👉 代理对象必须“包裹一个完整的原始对象”
总结
Spring Bean 的生命周期主要包括实例化、属性填充、初始化和销毁几个阶段。在实例化之后会进行依赖注入,然后如果实现了 Aware 接口会注入相关资源;在初始化阶段前后,Spring 通过 BeanPostProcessor 提供了两个扩展点,可以对 Bean 进行增强处理,比如 AOP 代理对象就是在 postProcessAfterInitialization 阶段生成的;此外,开发者也可以通过 @PostConstruct、InitializingBean 或 init-method 来自定义初始化逻辑,最后在容器关闭时执行销毁方法。
更新: 5/4/2026 字数: 0 字 时长: 0 分钟
