第一轮:基础热身,还能招架
面试官:请介绍一下 Java 中常见的集合类,ArrayList 和 LinkedList 有什么区别?
谢飞机:这个我会!ArrayList 是基于数组实现的,查询快,增删慢;LinkedList 是基于链表实现的,增删快,查询慢。
面试官:不错,理解到位。那 HashMap 呢?底层结构是怎样的?
谢飞机:HashMap 底层是数组 + 链表/红黑树。JDK8 开始,当链表长度超过 8 且数组长度大于 64 时,会转成红黑树,提高查找效率。
面试官:很好。那它怎么解决哈希冲突的?
谢飞机:用的是链地址法,相同 hash 值的元素放在同一个桶里,形成链表。
面试官:还可以。那如果多个线程同时 put,可能会出现什么问题?
谢飞机:嗯……会死锁?
面试官:不是死锁,是可能出现数据覆盖或者链表成环导致 CPU 100%。所以 HashMap 不是线程安全的。有了解过 ConcurrentHashMap 吗?
谢飞机:听说过,是线程安全的 HashMap,但具体咋实现的……我回去查过,忘了。
第二轮:多线程进阶,开始冒汗
面试官:我们系统有个定时任务,每分钟处理一万条订单,你会怎么设计线程模型?
谢飞机:我用 for 循环,每条 new 一个 Thread 来处理!
面试官:……你这服务器当场报废。为什么不使用线程池?
谢飞机:线程池?哦对,ThreadPoolExecutor!我知道有 corePoolSize、maximumPoolSize……剩下的我背过,但忘了。
面试官:那 workQueue 呢?常用的阻塞队列有哪些?
谢飞机:ArrayBlockingQueue、LinkedBlockingQueue……PriorityQueue?
面试官:PriorityQueue 不是线程安全的。那线程池的执行流程说一下?
谢飞机:先开核心线程,不够就丢队列,队列满再开最大线程,再不行就拒绝……拒绝策略有 AbortPolicy、CallerRunsPolicy……还有一个叫 Discard 的?
面试官:接近了。那你项目里有用过 Spring 的异步任务吗?@Async 注解?
谢飞机:用过!但我没配线程池,它自己开的。
面试官:那你知不知道默认用的是哪个线程池?
谢飞机:Simple… ThreadPool?No,好像是 Executors.newSimpleAsyncTaskExecutor……这不是线程池!
面试官:没错,它每次 new Thread,生产环境不能这么用。
第三轮:分布式与底层,彻底懵圈
面试官:我们订单系统用了 RabbitMQ,如果消息消费失败了怎么办?
谢飞机:我 catch 一下,然后打印日志……
面试官:然后呢?消息丢了?
谢飞机:那我再发一遍?
面试官:手动 ACK 呢?
谢飞机:啥是 ACK?
面试官:……那你了解 Redis 的持久化机制吗?
谢飞机:RDB 和 AOF,RDB 是拍快照,AOF 是记日志。但 RDB 会丢数据,AOF 文件太大。
面试官:那怎么优化 AOF?
谢飞机:我……重启 Redis?
面试官:可以重写(rewrite)压缩日志。MySQL 事务隔离级别了解吗?
谢飞机:有 read uncommitted、read committed、repeatable read……serializable。我们用的是 read committed。
面试官:那幻读是什么?怎么解决?
谢飞机:幻……幻想的读?应该是网络延迟吧。
面试官:……
面试官:今天就到这里吧,你的基础还有待加强。回去等通知吧。
谢飞机:好的,谢谢面试官,我回去就学!
答案解析:让你真能学会
1. ArrayList vs LinkedList
- ArrayList:基于动态数组,支持随机访问(O(1)),扩容机制为 1.5 倍增长。适合读多写少。
- LinkedList:双向链表,插入删除 O(1),但遍历慢。注意它实现了 Queue 接口,可用作队列。
2. HashMap 底层原理
- 数组 + 链表 + 红黑树(JDK8+)。
- 初始容量 16,负载因子 0.75,阈值 12,达到后扩容 2 倍。
- 哈希冲突:链地址法 + 红黑树优化。
- 线程不安全原因:多线程 put 可能导致链表成环(JDK7 头插法)、数据覆盖(JDK8 也有并发问题)。
3. ConcurrentHashMap 如何保证线程安全?
- JDK8:CAS + synchronized。对每个桶加锁,而不是整个 map。
- 分段锁思想升级,性能更高。
4. 线程池核心参数
corePoolSize:核心线程数,常驻内存。maximumPoolSize:最大线程数。workQueue:阻塞队列,如 LinkedBlockingQueue(无界)、ArrayBlockingQueue(有界)。keepAliveTime:非核心线程空闲存活时间。handler:拒绝策略,常见有:AbortPolicy:抛出异常(默认)CallerRunsPolicy:由调用线程执行任务DiscardPolicy:静默丢弃DiscardOldestPolicy:丢弃队列最前任务
5. 线程池执行流程
- 提交任务,若当前线程 < corePoolSize,创建新线程执行。
- 若 ≥ corePoolSize,放入 workQueue。
- 若队列满,且线程 < max,则创建非核心线程执行。
- 若线程 ≥ max 且队列满,执行拒绝策略。
6. @Async 默认线程池
- Spring 默认使用
SimpleAsyncTaskExecutor,不复用线程,每次新建,严禁用于生产环境。 - 正确做法:自定义 ThreadPoolTaskExecutor 并配置到 @EnableAsync。
7. RabbitMQ 消息可靠性
- 手动 ACK:消费者处理完后手动确认,否则重新入队或进入死信队列。
- 生产者确认机制(Confirm)+ 持久化(exchange、queue、message)确保不丢消息。
- 死信队列(DLX)处理失败消息。
8. Redis AOF 重写
bgrewriteaof命令触发,生成更紧凑的日志文件,避免无限增长。
9. MySQL 幻读
- 事务 A 两次查询,结果集不同,因为事务 B 插入了新记录。
- 解决方案:
- 可重复读(Repeatable Read)下通过 MVCC + 间隙锁(gap lock)防止幻读。
- 串行化(Serializable)彻底解决,但性能差。