1. 面试场景:Java技术栈深度拷问
"谢飞机是吧?先做个简单的自我介绍。"面试官推了推眼镜,面前这个穿着格子衬衫的候选人正襟危坐的样子让他想起五年前的自己。
"我叫谢飞机,会写'Hello World',精通Java的拼写..."谢飞机紧张地搓着手,简历上"精通分布式高并发"的字样在灯光下格外刺眼。
1.1 第一轮:Java基础与集合框架
面试官:"说说ArrayList和LinkedList的区别?"
谢飞机:"一个叫Array,一个叫Link,都是List的儿子!"(自信满满)
面试官:"(扶额)那HashMap的负载因子0.75是什么意思?"
谢飞机:"就像食堂打饭,75%的座位坐满就不让进了!"(突然灵光一闪)
面试官:"(意外地点头)这个比喻...居然有点道理。那ConcurrentHashMap怎么保证线程安全?"
谢飞机:"给每个桶配个保安?"(开始胡诌)
技术点解析:
- ArrayList基于动态数组,随机访问O(1),插入删除O(n);LinkedList基于双向链表,插入删除O(1),随机访问O(n)
- HashMap负载因子=扩容阈值/容量,0.75是空间与时间的平衡点,泊松分布证明碰撞概率最低
- JDK8的ConcurrentHashMap采用CAS+synchronized锁桶头节点,分段锁升级为更细粒度的控制
1.2 第二轮:JUC与多线程
面试官:"线程池的corePoolSize和maximumPoolSize怎么配合工作?"
谢飞机:"就像火锅店,常驻厨师是core,旺季招兼职是max!"(突然开窍)
面试官:"(眼前一亮)那ThreadLocal为什么会内存泄漏?"
谢飞机:"因为...线程洗澡不擦干?"(画风逐渐跑偏)
面试官:"AQS的CLH队列是什么数据结构?"
谢飞机:"肯德基豪华午餐队列?"(彻底放弃治疗)
技术点解析:
- 线程池工作流程:核心线程常驻→任务入队→非核心线程扩容→拒绝策略
- ThreadLocal的弱引用key被GC后,强引用value会导致Entry无法回收,需手动remove
- AQS通过CLH变种队列(双向链表+CAS)管理阻塞线程,实现公平锁
1.3 第三轮:框架与中间件
面试官:"Spring循环依赖怎么解决的?"
谢飞机:"你中有我,我中有你,建议他们结婚!"(开始摆烂)
面试官:"MyBatis的#和$区别是什么?"
谢飞机:"一个用花呗,一个用现金?"(试图蒙混过关)
面试官:"Redis的跳表为什么用ZSET?"
谢飞机:"因为...Z字比较帅?"(自暴自弃式回答)
技术点解析:
- Spring三级缓存:singletonFactories→earlySingletonObjects→singletonObjects
- #预处理参数防止SQL注入,$直接拼接(需手动过滤危险字符)
- 跳表(SkipList)时间复杂度O(logN),适合范围查询,与哈希表互补
2. 技术深度解析
2.1 HashMap底层实现
java复制// JDK8的节点结构
static class Node<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
V value;
Node<K,V> next;
}
哈希碰撞解决策略:
- 链表长度>8且数组长度≥64时转红黑树
- 树节点数<6时退化为链表
- 扰动函数:(h = key.hashCode()) ^ (h >>> 16)
2.2 线程池参数配置公式
math复制最大线程数 = (任务耗时/任务间隔时间) * 核心线程数
示例场景:
- IO密集型:核心线程数=CPU核数*2
- CPU密集型:核心线程数=CPU核数+1
2.3 Spring事务传播机制
| 传播行为 | 说明 |
|---|---|
| REQUIRED(默认) | 存在事务则加入,否则新建 |
| REQUIRES_NEW | 新建独立事务,挂起当前事务 |
| NESTED | 嵌套子事务,外层回滚会触发内层回滚 |
3. 面试官结语
"今天就到这里吧..."面试官合上笔记本,"有消息会通知你。"
谢飞机起身时突然回头:"其实我知道synchronized的锁升级过程!"
面试官挑眉:"哦?说说看。"
"就像打游戏,先单挑(偏向锁),不行就摇人(轻量锁),最后叫家长(重量锁)!"
面试官:"(憋笑)...门在那边。"
真实技术点:
- 锁升级路径:无锁→偏向锁→轻量锁→重量锁
- 偏向锁通过MarkWord记录线程ID
- 轻量锁用CAS自旋避免内核态切换
- 重量锁依赖操作系统mutex指令