大厂Java面试三轮实录:严肃面试官 VS 水货程序员谢飞机
大厂Java面试三轮实录严肃面试官 VS 水货程序员谢飞机文章内容人物介绍面试官互联网大厂资深技术专家说话严谨喜欢顺着业务场景层层追问。谢飞机五年经验简历写得满满当当气势很足真正进入深水区后逐渐暴露“水货”本质。第一轮Java 基础 集合 多线程基础场景背景面试官先从基础能力切入假设公司要开发一个高并发的活动报名系统看看谢飞机对 Java 基础和集合、多线程是否扎实。问题1HashMap为什么线程不安全在并发场景下怎么解决面试官我们有个活动报名系统会把用户报名信息先缓存在内存 Map 里。你说说HashMap为什么线程不安全并发场景怎么处理谢飞机HashMap不安全主要是因为它脾气不太稳定多线程一来它就容易“炸”。比如一个线程在放数据一个线程在拿数据就可能拿到不对的值。解决的话可以把HashMap外面套一层synchronized或者直接上ConcurrentHashMap反正名字里带Concurrent肯定就是并发专用的。面试官嗯基础点答到了。至少说明你没把HashMap和Hashtable当双胞胎。问题2ArrayList和LinkedList的区别报名名单这种场景该怎么选面试官如果报名成功的用户列表你准备用集合存储ArrayList和LinkedList有什么区别怎么选谢飞机ArrayList底层是数组查得快LinkedList底层是链表增删快。如果报名名单主要是追加那我觉得ArrayList比较好因为大家排着队报名就跟食堂打饭一样一个个往后站数组扩容一下也还能忍。面试官不错这个回答还算贴近场景没有一上来就说“都行看业务”。问题3创建线程有哪几种方式线程池为什么比手动创建线程更推荐面试官活动开始后要给报名用户异步发短信通知。你说说创建线程有哪些方式为什么更推荐线程池谢飞机创建线程嘛能new Thread能实现Runnable还能Callable。线程池更好是因为线程池里线程比较“团结”创建好后反复用省得老是 new像公司复用工位一样员工走了工位还在。而且线程池比较专业能控制线程数量不容易把机器跑冒烟。面试官这个比喻虽然离谱但意思对了。问题4线程池核心参数有哪些如果短信任务堆积你怎么排查面试官那你详细说说线程池核心参数。假如短信发送任务堆积了你会怎么分析谢飞机线程池参数有核心线程数、最大线程数、队列还有什么存活时间……还有拒绝策略就是线程池实在忙不过来就开始“拒客”。如果任务堆积我一般先看看是不是线程不够再看看队列是不是太长或者短信平台是不是抽风了。实在不行就把线程数调大一点先顶住。面试官嗯能说出参数但分析不够深入。线程池不是拧水龙头不是越大越好。第二轮JUC JVM Spring MyBatis场景背景基础过后面试官开始增加难度。假设这个活动系统已经上线报名高峰期出现响应变慢、接口超时、数据库压力大等问题。问题1synchronized和ReentrantLock有什么区别面试官报名接口里为了防止重复报名你会加锁。那synchronized和ReentrantLock有什么区别谢飞机synchronized就是 JVM 官方锁比较“正统”ReentrantLock是 Java 代码级别的锁比较“灵活”。区别嘛一个写起来简单一个功能多比如可以手动加锁解锁。ReentrantLock听名字就很高级适合复杂场景。面试官你这个回答属于“方向对了但没下高速”。问题2说说 JVM 内存结构和垃圾回收为什么会发生 Full GC面试官系统高峰期突然卡顿监控发现 Full GC 次数很多。你说说 JVM 内存结构为什么可能频繁 Full GC谢飞机JVM 内存结构主要有堆、栈、方法区这些。对象一般在堆里方法在线程里跑。Full GC 多可能就是对象太多了JVM 清不过来。也有可能代码写得比较“热情”不停 new。再或者内存小了GC 就会更勤快一些。面试官比“重启试试”强一点但还没达到能排障的程度。问题3Spring 中Bean的生命周期大致是什么面试官如果你们在 Spring 容器里管理报名服务组件那Bean生命周期大致有哪些阶段谢飞机Spring Bean 生命周期大概就是先创建对象再注入属性然后初始化最后销毁。中间如果实现了一些接口还能插点自己的逻辑。反正 Spring 就像物业帮你把对象从出生照顾到退休。面试官嗯这个说法通俗基础还行。问题4MyBatis 一级缓存和二级缓存有什么区别为什么很多公司谨慎开启二级缓存面试官报名记录查询比较频繁。你说说 MyBatis 一级缓存和二级缓存区别为什么很多公司对二级缓存很谨慎谢飞机一级缓存我记得是 SqlSession 级别二级缓存好像是 Mapper 级别或者更大范围。很多公司不用二级缓存可能是怕缓存不准毕竟数据库数据一变缓存如果没跟上就容易查出“前朝数据”。而且现在大家都喜欢 Redis这样显得架构更高级。面试官前半段是技术后半段是情绪。第三轮分布式架构 中间件 数据库 运维 设计场景背景面试官继续深入活动系统需要拆分成用户服务、报名服务、通知服务还要支持定时任务、消息削峰、缓存、容器部署等。问题1如果用 Dubbo 拆分服务服务注册与调用流程大致是什么面试官现在系统拆分成多个微服务用 Dubbo 做 RPC。你说说服务注册与调用流程。谢飞机Dubbo 就是服务提供者把自己注册到注册中心消费者去注册中心找服务然后双方就快乐通信。调用时消费者应该能拿到提供者地址然后发请求过去。注册中心像婚介所负责给双方牵线。面试官比喻越来越多细节越来越少。问题2RabbitMQ 在活动场景里怎么用如果消息重复消费怎么办面试官活动开始瞬间流量很大我们打算把发券、发短信这些操作异步化用 RabbitMQ 削峰。那消息重复消费怎么处理谢飞机RabbitMQ 就是先把消息存进去消费者慢慢处理这样系统不会一下子被冲垮。重复消费的话可以判断一下这个消息是不是处理过。比如搞个表或者 Redis 记一下处理过就别再来。总之核心思想就是“来过一次就别想白嫖第二次”。面试官这个回答还行至少知道幂等。问题3Redis 缓存和 MySQL 如何保证一致性面试官报名结果会先查 Redis查不到再查 MySQL。那缓存和数据库怎么保证一致性谢飞机一致性这个问题比较哲学。一般就是更新数据库的时候顺手更新缓存或者先删缓存再更数据库再或者先更数据库再删缓存。我以前项目里一般采用“差不多一致”策略因为互联网系统追求的是优雅不一定每一秒都完全同步。面试官你把最终一致性说成了人生感悟。问题4MySQL 如何优化一条慢查询面试官报名列表接口很慢你会怎么优化 MySQL 慢查询谢飞机先看 SQL再看索引再看执行计划。如果还慢我会考虑是不是表太大了可以分页、加索引、改 SQL、分库分表必要时上缓存。总之数据库慢不是它想慢是我们给它的压力太沉重。面试官套话很完整但缺细节。问题5你理解的 DDD 是什么活动报名这个系统如何做领域划分面试官最后一个问题说说你理解的 DDD。以活动报名系统为例怎么做领域划分谢飞机DDD 我理解就是代码要围着业务转而不是围着表转。领域划分的话可以分用户域、活动域、报名域、通知域。这样听起来就很专业。再往下还能分聚合、实体、值对象但我一般都是在设计文档里写代码里看情况发挥。面试官好的今天先到这里。你先回去等通知如果我们后续还有疑问会联系你。谢飞机好的老师我手机 24 小时开机除了没信号的时候。面试问题详细答案整理下面把上面提到的问题做系统化讲解方便小白学习和复盘。1. HashMap 为什么线程不安全并发场景怎么解决1.1 为什么线程不安全HashMap在多线程环境下没有加锁保护多个线程同时执行put、resize、get等操作时可能出现数据覆盖数据丢失读取到不一致的数据JDK 1.7 中扩容时甚至可能形成链表环导致死循环1.2 底层原因HashMap的核心结构是数组 链表/红黑树。多线程同时修改同一个桶中的数据或者同时触发扩容迁移都可能造成结构混乱。1.3 并发场景解决方案Collections.synchronizedMap()简单粗暴整体加锁性能一般。Hashtable方法基本都加了synchronized线程安全但性能较低。ConcurrentHashMap高并发场景优先选择。1.4 ConcurrentHashMap 为什么更适合JDK 1.7 使用分段锁 SegmentJDK 1.8 使用CAS synchronized锁粒度更细支持更高并发读写性能2. ArrayList 和 LinkedList 的区别2.1 底层结构ArrayList动态数组LinkedList双向链表2.2 特点对比ArrayList随机访问快get(index)快尾部追加效率高中间插入/删除可能需要大量元素移动LinkedList插入删除某个已知位置元素效率高随机访问慢需要顺序遍历更占内存2.3 场景选择如果业务主要是查询多尾部追加多遍历多一般优先选ArrayList。3. 创建线程的方式与线程池优势3.1 创建线程常见方式继承Thread实现Runnable实现CallableFuture使用线程池ExecutorService3.2 为什么推荐线程池减少频繁创建/销毁线程带来的开销提高响应速度统一管理线程资源控制并发数量防止系统资源耗尽支持任务队列、拒绝策略、监控扩展4. 线程池核心参数与任务堆积排查4.1 ThreadPoolExecutor 核心参数ThreadPoolExecutor( int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueueRunnable workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler)4.2 参数说明corePoolSize核心线程数maximumPoolSize最大线程数keepAliveTime非核心线程空闲存活时间workQueue任务队列threadFactory线程工厂handler拒绝策略4.3 任务堆积如何分析看任务执行时间是否过长看队列是否过大是否造成大量请求堆积看核心线程数/最大线程数是否合理看下游依赖是否变慢例如短信接口、数据库、网络结合监控指标活跃线程数、队列长度、任务耗时、拒绝次数4.4 常见误区不是线程越多越好CPU 密集型任务线程数通常接近 CPU 核数IO 密集型任务可适当放大线程数5. synchronized 和 ReentrantLock 的区别5.1 相同点都能实现线程同步保证共享资源在同一时刻只被有限线程访问。5.2 区别synchronizedJava 关键字自动加锁、自动释放锁使用简单JVM 层面实现ReentrantLockJUC 提供的显示锁需要手动lock()/unlock()支持可中断锁、可超时获取锁、公平锁功能更灵活5.3 使用建议简单同步优先synchronized高级控制需求用ReentrantLock6. JVM 内存结构与 Full GC6.1 JVM 运行时数据区程序计数器虚拟机栈本地方法栈堆方法区/元空间6.2 对象主要存在哪里大多数对象在堆中分配新生代老年代6.3 什么是 Full GCFull GC 通常是对老年代、部分收集器下也会涉及整个堆和方法区/元空间的垃圾回收暂停时间通常较长。6.4 Full GC 频繁原因老年代空间不足大对象直接进入老年代晋升过快内存泄漏元空间不足显式调用System.gc()不合理的 JVM 参数配置6.5 排查思路看 GC 日志看堆内存使用情况用jmap、jstack、MAT 等工具分析检查是否存在对象长期被引用无法释放7. Spring Bean 生命周期7.1 主要流程实例化 Bean属性注入调用BeanNameAware、BeanFactoryAware等感知接口BeanPostProcessor前置处理调用初始化方法InitializingBean.afterPropertiesSet()自定义init-methodBeanPostProcessor后置处理Bean 可正常使用容器关闭时调用销毁方法DisposableBean.destroy()自定义destroy-method8. MyBatis 一级缓存和二级缓存8.1 一级缓存默认开启作用域SqlSession同一个SqlSession中相同查询可能直接走缓存8.2 二级缓存需要手动开启作用域Mapper 级别 / namespace 级别多个SqlSession可共享8.3 为什么很多公司谨慎开启二级缓存数据一致性难控制更新操作后缓存失效策略复杂分布式场景下不如 Redis 这类集中式缓存可控容易引入脏数据问题9. Dubbo 服务注册与调用流程9.1 核心角色Provider服务提供者Consumer服务消费者Registry注册中心Monitor监控中心9.2 流程Provider 启动后将服务地址注册到注册中心Consumer 启动后从注册中心订阅服务列表注册中心变更时通知 ConsumerConsumer 基于负载均衡策略选择一个 Provider 调用通过 Dubbo 协议进行远程调用9.3 Dubbo 的价值高性能 RPC服务治理能力强支持负载均衡、容错、路由、监控10. RabbitMQ 如何处理重复消费10.1 为什么会重复消费消息系统为了保证可靠性可能会出现消费者处理成功但 ACK 丢失网络抖动导致重复投递消费端重启后重复拉取10.2 核心解决思路幂等无论消息消费一次还是多次最终结果都应一致。10.3 常见幂等方案数据库唯一索引Redis 去重消费记录表基于业务状态机判断例如发券时以userId couponId建唯一索引发短信时记录 messageId已处理则跳过11. Redis 与 MySQL 的一致性方案11.1 常见策略方案一先更新数据库再删除缓存这是实际中比较常见的方案。流程更新 MySQL删除 Redis 缓存下次读请求缓存未命中再从数据库加载并回填缓存11.2 为什么不推荐先删缓存再更新数据库中间如果有并发读请求可能把旧数据再次写回缓存。11.3 延迟双删为降低并发场景脏数据问题可采用删除缓存更新数据库延迟一小段时间后再次删除缓存11.4 最终一致性分布式系统里很难做到绝对强一致很多互联网场景采用最终一致性即可。12. MySQL 慢查询优化思路12.1 排查步骤开启慢查询日志找到慢 SQL使用EXPLAIN分析执行计划看是否命中索引看是否存在全表扫描、回表过多、排序、临时表12.2 常见优化手段建立合适索引避免select *优化 where 条件顺序避免索引失效对字段做函数运算隐式类型转换前导模糊查询%xxx分页优化大表拆分读写分离引入缓存12.3 索引设计原则高区分度字段优先高频查询条件优先联合索引遵循最左前缀原则不要滥建索引索引会影响写入性能13. DDD 的理解与领域划分13.1 什么是 DDDDDD 即领域驱动设计核心思想是以业务领域为核心建模让代码结构贴近业务语义通过统一语言连接产品、业务、开发13.2 DDD 常见概念Entity实体有唯一标识例如活动、用户、报名单Value Object值对象无唯一标识例如地址、金额Aggregate聚合一组相关对象的一致性边界Aggregate Root聚合根聚合对外访问入口Domain Service领域服务不适合放在实体中的领域逻辑Repository仓储负责聚合的持久化13.3 活动报名系统如何划分领域可以拆分为用户域用户注册、登录、身份信息活动域活动创建、配置、状态管理报名域报名、取消报名、名额控制、资格校验通知域短信、站内信、邮件券域/奖励域发券、领奖、积分发放13.4 DDD 的价值减少“表结构驱动开发”问题提高复杂业务可维护性更适合中大型复杂系统14. 补充高频知识点速记14.1 Redis 常见数据类型StringHashListSetZSetBitmapHyperLogLogStream14.2 SpringBoot 优势自动配置起步依赖简化版本管理内嵌 Web 容器更适合快速开发微服务14.3 Linux 常用排查命令top查看系统资源ps -ef | grep查看进程netstat -tunlp/ss -lntp查看端口tail -f实时查看日志grep搜索日志关键字df -h查看磁盘free -m查看内存14.4 Docker 常见作用环境隔离快速部署保证开发、测试、生产环境一致提升交付效率14.5 常见设计模式单例模式工厂模式策略模式模板方法模式责任链模式观察者模式代理模式总结这场面试看似搞笑本质上暴露了一个典型问题很多人简历上技术栈写得很多但真正到了面试里能不能把知识点讲清楚、讲透彻、讲到业务场景里才是关键。如果你正在准备 Java 面试建议按下面的路线复习Java 基础与集合并发编程与线程池JVM 与性能排查Spring / SpringBoot / MyBatisRedis / MySQL / MQ微服务与分布式Linux / Docker / 设计模式 / DDD记住面试官最怕你背八股最喜欢你把“原理 场景 方案 优缺点”讲完整。祝你面试时不像谢飞机至少在第三轮之前别露馅。