谢飞机大战互联网大厂
# 面试实录谢飞机大战互联网大厂从 HashMap 到 DDD 的“灵魂”拷问**文章内容**会议室里冷气开得很足大厂的 Logo 在墙上显得格外严肃。面试官老张推了推眼镜目光如炬地盯着对面的求职者——谢飞机。谢飞机穿着一件印着“代码改变世界”的 T 恤脸上挂着自信却略显油腻的笑容。**第一轮基础与集合的“陷阱”**老张翻开简历语气平淡“谢飞机我们先从基础开始。你说你精通 Java 核心那先聊聊 HashMap。在 JDK 1.8 之后HashMap 的底层结构发生了什么变化为什么”谢飞机一听眼睛一亮“这个太简单了1.7 是数组加链表1.8 加了红黑树。因为链表太长查询慢变成树就快了时间复杂度从 O(n) 变成了 O(log n)。”老张微微点头“不错反应很快。那如果两个对象的 hashCode 相同但 equals 返回 false在 HashMap 中会发生什么怎么解决”谢飞机挠了挠头“呃……那就是哈希冲突嘛。解决方法就是……用红黑树或者……重新计算哈希反正就是不让它们打架。”老张没说话继续追问“那 ArrayList 和 LinkedList 的区别是什么在什么场景下你会优先选 LinkedList”“这个我会”谢飞机拍大腿ArrayList 是数组增删慢LinkedList 是链表增删快。所以我要频繁增删就用 LinkedList比如……比如写个贪吃蛇游戏”老张嘴角抽搐了一下“贪吃蛇场景确实需要频繁增删但实际业务中LinkedList 的内存开销大且缓存不友好。那如果 ArrayList 扩容数据怎么迁移线程安全吗”谢飞机眼神开始飘忽“扩容就是……翻倍数据迁移就是……复制过去线程安全……如果不加锁就不安全加锁就安全比如用 Vector”老张合上本子叹了口气“基础尚可但细节模糊。我们进入下一轮聊聊并发。”**第二轮JUC 与多线程的“胡言乱语”**“假设你正在做一个高并发的秒杀系统”老张身体前倾“你会怎么设计线程池核心参数怎么设置如果队列满了拒绝策略选什么”谢飞机深吸一口气“秒杀系统嘛肯定要用 ThreadPoolExecutor。核心线程数设成 CPU 核数最大线程数设成两倍。队列用 LinkedBlockingQueue拒绝策略……选 CallerRunsPolicy让调用者自己跑这样系统就不会崩。”老张眉头紧锁“为什么最大线程数设成两倍如果 IO 密集型任务呢CallerRunsPolicy 在秒杀场景下真的合适吗会不会导致主线程阻塞”谢飞机支支吾吾IO 密集型……那就设大点主线程阻塞……应该没事吧反正它自己跑得快。再说了线程池就是用来干活的活多了就自己干呗。”老张没再纠结直接抛出重锤“好那聊聊 synchronized 和 ReentrantLock 的区别。ReentrantLock 的公平锁和非公平锁底层是怎么实现的AQS 是什么”谢飞机一脸茫然“区别就是……一个关键字一个类。AQS……好像是……自动锁公平锁就是排队非公平锁就是插队。底层实现……就是用了个原子操作CAS 吧具体怎么实现的我也没看过源码反正能用就行。”老张眼神中透出一丝失望“那如果多个线程同时修改同一个对象怎么保证可见性和有序性volatile 关键字能解决所有并发问题吗”“当然不能”谢飞机大声说volatile 只能保证可见性不能保证原子性。要解决原子性就得加锁。有序性……好像也是 volatile 管不对好像是内存屏障”老张揉了揉太阳穴“看来并发这块你只停留在 API 调用层面。我们最后聊聊框架和架构。”**第三轮Spring、Redis 与 DDD 的“终极幻想”**“说说 Spring 的 Bean 生命周期”老张语气放缓试图做最后的抢救“以及 SpringBoot 的自动装配原理是什么”谢飞机如释重负“生命周期就是……实例化、属性填充、初始化、销毁。自动装配……就是 SpringBootApplication 那个注解它会自动扫描包把配置好的 Bean 都装进去。原理就是……魔法”老张强忍着笑意“那 MyBatis 的一级缓存和二级缓存有什么区别在分布式环境下二级缓存怎么用Dubbo 的负载均衡策略有哪些”“一级缓存是……方法内的二级缓存是……全局的”谢飞机越说越虚“分布式环境下……二级缓存不能用了因为多实例数据不一致。Dubbo 的负载均衡……有随机、轮询、最少活跃调用数。还有……一致性哈希”老张追问“如果 Redis 缓存穿透、击穿、雪崩怎么解决RabbitMQ 怎么保证消息不丢失XXL-JOB 的分布式调度原理是什么”谢飞机开始胡编乱造“缓存穿透……就存个空值呗。击穿……加锁。雪崩……设置不同的过期时间。消息不丢失……开启确认机制ACK 嘛。XXL-JOB……就是定时任务多个节点跑谁抢到谁执行原理就是……心跳检测”老张最后问了一个压轴题“最后谈谈你对 DDD领域驱动设计的理解。在微服务架构下如何划分限界上下文聚合根和实体有什么区别”谢飞机站起来双手比划“DDD 就是……把业务逻辑和代码分开。限界上下文就是……把大模块切成小模块。聚合根……就是那个最重要的对象实体就是……普通的对象。区别就是……聚合根有 ID实体没有”老张沉默了整整十秒看着谢飞机期待的眼神缓缓说道“谢飞机你的热情我很欣赏基础概念也懂一些但在深度、原理和复杂场景的解决方案上还有很长的路要走。大厂的技术栈不仅需要会用更需要懂底层、懂设计。你先回去等通知吧不过……建议你回去先把《Java 并发编程实战》和《深入理解 Java 虚拟机》认真读一遍。”谢飞机愣了一下尴尬地笑了笑“好嘞谢谢面试官那我走了再见”看着谢飞机离去的背影老张在评分表上写下了一行字**“基础尚可原理薄弱缺乏深度不予通过。”**---**技术问答详解小白学习版****1. HashMap 底层结构与扩容*** **JDK 1.8 变化**1.7 是“数组 链表”1.8 是“数组 链表 红黑树”。当链表长度超过 8 且数组长度超过 64 时链表转为红黑树查询效率从 O(n) 提升至 O(log n)。* **哈希冲突**如果 hashCode 相同但 equals 为 false说明是不同对象但哈希值冲突。HashMap 会通过链表或红黑树存储这些对象遍历时通过 equals 判断键值是否相等。* **扩容机制**当元素数量超过 threshold容量 * 负载因子默认 0.75时触发扩容。新容量为旧容量的 2 倍。扩容时原数组元素会重新计算哈希值rehash根据新容量决定是留在原位还是移到新位置下标 原容量。* **线程安全**HashMap **不是**线程安全的。多线程下扩容可能导致死循环1.7或数据覆盖1.8。高并发场景应使用 ConcurrentHashMap。**2. ArrayList 与 LinkedList 及扩容*** **区别**ArrayList 基于动态数组支持随机访问O(1)但中间增删需移动元素O(n)LinkedList 基于双向链表增删快O(1)前提是已定位到节点但随机访问慢O(n)。* **场景**除非是频繁在头部/尾部增删且不需要随机访问否则**绝大多数场景首选 ArrayList**因为其内存连续CPU 缓存友好。* **扩容**默认初始容量 10。扩容时创建新数组旧容量 * 1.5将旧数据拷贝过去。**3. 线程池与并发核心*** **线程池参数*** corePoolSize核心线程数常驻内存。* maximumPoolSize最大线程数。* workQueue任务队列如 ArrayBlockingQueue、LinkedBlockingQueue。* keepAliveTime非核心线程空闲存活时间。* RejectedExecutionHandler拒绝策略。* **秒杀场景设计*** 线程数IO 密集型如查库、调接口通常设为 CPU 核数 * 2 或更多CPU 密集型设为 CPU 核数 1。* 队列建议使用有界队列避免内存溢出。* 拒绝策略秒杀场景通常**不选** CallerRunsPolicy会导致主线程阻塞响应变慢而是自定义策略如直接丢弃、记录日志或返回友好提示。* **synchronized vs ReentrantLock*** synchronizedJVM 层面实现自动加锁/释放不可中断非公平锁。* ReentrantLockAPI 层面需手动 lock()/unlock()可中断支持公平/非公平支持多条件变量。* **AQS (AbstractQueuedSynchronizer)**JUC 包的核心框架。通过一个 volatile int state 变量表示同步状态配合 FIFO 队列管理等待线程。ReentrantLock、CountDownLatch 等都基于 AQS 实现。* **volatile*** **可见性**一个线程修改其他线程立即可见。* **有序性**禁止指令重排序通过内存屏障。* **原子性****不保证**。例如 i 操作读 - 改 - 写仍需 synchronized 或 AtomicInteger。**4. Spring 与 SpringBoot*** **Bean 生命周期**实例化 - 属性赋值 - 初始化BeanPostProcessor 前后处理、InitializingBean、init-method- 使用 - 销毁。* **自动装配原理**通过 EnableAutoConfiguration 开启底层使用 SpringFactoriesLoader 加载 META-INF/spring.factories 文件中的配置类结合 Conditional 注解如 ConditionalOnClass按需加载 Bean。**5. 数据库与中间件*** **MyBatis 缓存*** 一级缓存SqlSession 级别默认开启同一次会话内查询相同 SQL 直接返回缓存。* 二级缓存Mapper 级别需手动开启。在分布式环境下由于多个实例内存隔离**通常不建议开启二级缓存**应使用 Redis 等集中式缓存。* **Dubbo 负载均衡**Random随机、RoundRobin轮询、LeastActive最少活跃调用数、ConsistentHash一致性哈希适合有状态服务。* **Redis 缓存问题*** **穿透**查不存在的数据布隆过滤器、缓存空对象。* **击穿**热点 Key 过期互斥锁、逻辑过期不设 TTL异步更新。* **雪崩**大量 Key 同时过期随机过期时间、高可用集群、限流降级。* **RabbitMQ 消息可靠性*** 生产者开启 Confirm 模式。* 中间件开启持久化Exchange、Queue、Message。* 消费者手动 ACK处理完业务再确认。* **XXL-JOB**基于 Quartz 改进采用分布式调度中心 执行器模式。通过心跳机制发现执行器调度中心下发任务执行器拉取并执行支持分片广播、故障转移。**6. DDD领域驱动设计*** **核心概念*** **限界上下文Bounded Context**明确界定业务模型的边界不同上下文同一词汇含义可能不同如“订单”在销售上下文和物流上下文中属性不同。* **聚合根Aggregate Root**聚合的入口保证聚合内数据一致性外部只能通过聚合根访问内部实体。* **实体Entity**有唯一标识的对象。* **值对象Value Object**无唯一标识通过属性值判断相等不可变。* **微服务划分**通常依据限界上下文来划分微服务确保服务间高内聚、低耦合。**文章标签**Java, 面试, 大厂, 后端开发, 技术分享, 谢飞机, 并发编程, SpringBoot, 系统设计**文章简述**本文通过幽默的“谢飞机”面试故事生动还原了互联网大厂 Java 面试现场。从 HashMap 底层到 DDD 架构展示了“水货”程序员在基础与深度问题上的真实反应。文末附带详细的技术解析涵盖 JUC、JVM、中间件等核心考点适合求职者查漏补缺与小白入门学习。