Java面试翻车现场:谢飞机大战严肃面试官,3轮提问笑到头掉!
Java面试翻车现场谢飞机大战严肃面试官3轮提问笑到头掉场景介绍时间一个阳光明媚的下午地点某互联网大厂面试间人物面试官·张工头顶微秃眼神犀利T恤外面套着格子衬衫面无表情手边放着一杯早已凉透的美式咖啡候选人·谢飞机号称精通Java简历上写着熟练掌握各类中间件实际上...你懂的第一轮Java核心基础の碾压面试官翻着简历眉头微微一皱感觉事情并不简单面试官面无表情先做个自我介绍吧。谢飞机坐直身子清了清嗓子面试官你好我叫谢飞机毕业两年实际工作经验三年半精通Java、Spring全家桶、Redis、MySQL、消息队列、Docker...基本上后端那一套我都熟面试官内心OS两年经验说三年半简历上写的是精通现在面试最怕听到精通...好那咱们直接开始技术面。问题1HashMap的底层实现原理是什么面试官说说HashMap吧底层怎么实现的谢飞机自信满满HashMap啊就是用来存键值对的底层是数组链表红黑树。JDK 1.8之后当链表长度超过8的时候就会转成红黑树查询效率从O(n)变成O(log n)面试官点点头嗯那为什么阈值是8呢谢飞机愣了一下开始胡说因为...7是质数啊8比7大一点而且您想啊7上8下嘛链表长了要上天变成树所以是8面试官嘴角抽动...那负载因子为什么是0.75谢飞机彻底放飞0.75啊这是经过大量数学推导的空间和时间的一个平衡点。但是我觉得吧如果内存不值钱负载因子设成1也行就是碰撞多点呗。就像相亲标准放低了负载因子大见的候选人多了冲突就多了但没见着合适的还得继续找...面试官赶紧打断好了好了下一个问题。问题2ArrayList 和 LinkedList 有什么区别面试官ArrayList和LinkedList的区别说一下。谢飞机这个简单ArrayList底层是数组LinkedList底层是双向链表。ArrayList查询快LinkedList增删快。但是面试官警觉但是什么谢飞机但是我上次在ArrayList中间位置插入了100万条数据慢得我想砸电脑然后我换成LinkedList结果更慢了面试官您说这是为什么呀面试官被反客为主沉默三秒LinkedList的中间插入虽然是O(1)...但你需要先找到那个位置而查找是O(n)的。谢飞机哦原来如此那我可以每天跟您学点新知识吗面试官面无表情...下一个问题。问题3 和 equals 有什么区别面试官和equals的区别你来说说。谢飞机这题我会比较的是内存地址equals比较的是内容。但是String是个特例String重写了equals方法所以比较的是值面试官嗯那这段代码输出什么Integer a 127; Integer b 127; System.out.println(a b);谢飞机false因为...不对等一下。Integer有缓存池-128到127之间的整数会被缓存所以...是true面试官那Integer a 128; Integer b 128;呢谢飞机false因为128超出了缓存范围会创建新对象。但是面试官我想问个问题如果我用new Integer(127)呢面试官微微点头那你觉得呢谢飞机new了就是新对象肯定是false这个坑我踩过之前写代码因为这个问题线上出了bug被老大骂了三天三夜...面试官基础还可以但细节要注意。问题4String、StringBuffer、StringBuilder 的区别面试官说说三者的区别。谢飞机String是不可变的每次拼接都会产生新对象。StringBuffer和StringBuilder是可变的。StringBuffer加了synchronized线程安全但慢StringBuilder没加锁线程不安全但快。面试官嗯那在实际开发中怎么选谢飞机我觉得吧现在谁还用StringBuffer啊直接在方法内部用StringBuilder不就行了方法内不存在线程安全问题。除非您是在多线程环境下共享变量拼接字符串但这种情况本身就很少见。就像您出门吃个饭还用得着带个保镖synchronized吗面试官难得地嘴角上扬了一下有点道理。第一轮还行咱们继续。第二轮多线程 JUC の 灵魂拷问面试官喝了一口凉透的咖啡眼神突然凌厉起来面试官基础还行那我们聊聊多线程。谢飞机搓搓手来这个我擅长问题1volatile 关键字你是怎么理解的面试官说说volatile。谢飞机突然正经volatile是Java提供的最轻量级的同步机制它有两个特性第一保证可见性一个线程修改了变量其他线程能立即看到第二禁止指令重排序面试官有点意外哦底层怎么实现的谢飞机底层用的是内存屏障在volatile写操作前插入StoreStore屏障写操作后插入StoreLoad屏障在读操作前插入LoadLoad屏障读操作后插入LoadStore屏障我看过《深入理解Java虚拟机》面试官眼神里闪过一丝赞赏不错。那你觉得volatile能保证原子性吗谢飞机不能比如count这种操作volatile管不了因为count不是原子操作分成了读取、修改、写入三步。就像您数钱数到一半被人打断回来就忘了数到哪了。面试官那怎么解决谢飞机用AtomicInteger啊底层是CASUnsafe类自旋或者加synchronized。面试官点头嗯这块学得不错。问题2synchronized 和 ReentrantLock 的区别面试官synchronized和ReentrantLock有什么区别谢飞机synchronized是关键字JVM层面实现的ReentrantLock是API层面的锁。synchronized自动加锁解锁ReentrantLock要手动lock()和unlock()。面试官还有呢谢飞机ReentrantLock可以实现公平锁synchronized只能是非公平的。ReentrantLock可以通过Condition实现分组唤醒线程就像把线程分到不同的休息室想叫醒哪个叫醒哪个synchronized要么用notify随机唤醒一个要么用notifyAll全部唤醒太粗糙了面试官那性能上呢谢飞机现在synchronized优化后性能跟ReentrantLock差不多了JDK 1.6之后加了偏向锁、轻量级锁、锁升级机制。不过...面试官我上次写了个死锁debug了三天才发现是锁的顺序反了您说这算不算一种经验面试官扶额算...算吧。问题3线程池的核心参数讲一下面试官线程池的核心参数有哪些谢飞机7大参数corePoolSize核心线程数、maximumPoolSize最大线程数、keepAliveTime空闲存活时间、TimeUnit时间单位、workQueue工作队列、ThreadFactory线程工厂、RejectedExecutionHandler拒绝策略面试官那如果corePoolSize5maximumPoolSize10workQueue容量是3来了12个任务过程是怎样的谢飞机开始手舞足蹈首先前5个任务创建核心线程处理然后接下来的3个任务进队列排队再然后队列满了剩下的任务...等等我数一下12-5-34个但最大线程是10已经用了5个核心线程还能再创建10-55个临时线程所以4个任务都能用临时线程处理面试官那如果来15个任务呢谢飞机15-510个任务排队不对...前5个核心线程处理接下来3个进队列队列容量3再接下来...5...我算算5个核心线程处理5个3个进队列还能创建5个临时线程处理5个一共处理了53513个还有2个...触发拒绝策略面试官点了点头流程对了。拒绝策略有哪些谢飞机4种AbortPolicy抛异常、CallerRunsPolicy调用者线程自己执行、DiscardPolicy直接丢弃、DiscardOldestPolicy丢弃队列最老的任务。我一般用CallerRunsPolicy让调用者线程自己跑就像家长不管孩子了让孩子自己管自己面试官若有所思嗯...第二轮表现不错。第三轮框架 中间件 数据库 の 终极乱斗面试官推了推眼镜决定加大难度问题1Spring IOC 和 AOP 的理解面试官说说Spring的IOC和AOP。谢飞机IOC就是控制反转原来我们new对象现在把对象创建交给Spring容器管理。就像以前自己买菜做饭现在点外卖让美团送面试官忍住没笑那DI呢谢飞机DI是依赖注入是IOC的具体实现方式可以通过构造器注入、setter注入、字段注入。但是我听说Spring官方推荐用构造器注入因为字段注入容易导致空指针而且不方便做单元测试。面试官AOP呢谢飞机AOP是面向切面编程可以在不修改源码的情况下增加功能。比如日志、事务、权限校验。底层是动态代理有接口用JDK动态代理没接口用CGLIB代理。面试官那JDK动态代理和CGLIB有什么区别谢飞机JDK动态代理只能代理有接口的类通过反射生成代理对象CGLIB是通过继承生成子类重写方法实现代理。不过现在SpringBoot默认用CGLIB了。对了面试官我上次自己写了个AOP切面结果一直不生效debug了一天发现是没加EnableAspectJAutoProxy注解...面试官同情地看了他一眼SpringBoot其实不需要手动加这个。谢飞机对这就是我后来才知道的...问题2Redis缓存穿透、缓存击穿、缓存雪崩面试官说说Redis缓存穿透、缓存击穿、缓存雪崩的区别和解决方案。谢飞机深吸一口气缓存穿透是查一个不存在的数据缓存和数据库都没有每次请求都打到数据库。解决方案缓存空值设置较短的过期时间或者用布隆过滤器面试官布隆过滤器原理知道吗谢飞机知道布隆过滤器用多个哈希函数把key映射到bitmap上。判断一个key不存在那它一定不存在判断一个key存在它不一定存在。就像我女朋友查我手机没找到证据不代表我没问题但找到证据我就肯定有问题面试官尴尬地咳嗽...那缓存击穿呢谢飞机缓存击穿是某个热点key过期了大量请求同时打过来。解决方案用互斥锁只让一个线程去查数据库重建缓存其他线程等待。或者设置热点key永不过期后台异步更新。面试官缓存雪崩谢飞机缓存雪崩是大批量的key在同一时间过期或者Redis挂了解决方案过期时间加随机值避免集体过期Redis用集群高可用本地缓存限流降级。就像疫情期间大家同时去超市抢菜菜没了就出大事了所以要分批去面试官嗯说得还行。问题3MySQL索引失效的场景有哪些面试官哪些情况会导致MySQL索引失效谢飞机这个我熟第一对索引列使用函数比如WHERE SUBSTR(name,1,3)abc第二隐式类型转换比如字段是int类型但传了字符串第三不符合最左前缀原则第四like以%开头第五使用OR条件且OR两边不全是索引列第六数据量很小的时候MySQL可能会走全表扫描...面试官嗯那联合索引(a, b, c)查询WHERE a1 AND c3用到了吗谢飞机用到了a但c没用到因为b断了就像您追女朋友第一步认识了第三步直接求婚中间第二步约会跳过了您说能成吗面试官一脸黑线...这比喻还挺贴切。问题4RabbitMQ消息丢失怎么处理面试官RabbitMQ消息丢失的场景和处理方式。谢飞机消息丢失分三种情况第一种生产者发送时丢失。用confirm机制生产者发送消息后Broker返回ACK确认。没收到ACK就重发。第二种RabbitMQ自身丢失。开启持久化交换机、队列、消息都要持久化。还要用镜像队列做高可用。第三种消费者处理时丢失。用手动ACK消费成功才手动确认失败了就不确认消息会重回队列。面试官那消息重复消费怎么办谢飞机幂等性处理比如数据库用唯一索引或者Redis存消息ID做去重。不过面试官我上次把消息队列搞炸了几百万消息堆积我直接写了个脚本删了队列重建被运维大哥追着打了三层楼...面试官擦了擦汗你们公司运维脾气还挺好没直接开除你谢飞机因为是我小舅子...面试官...问题5DDD领域驱动设计你怎么看面试官最后一个问题你对DDD领域驱动设计有什么理解谢飞机眼睛一亮DDD这个我懂就是Domain-Driven Design核心是聚合根、实体、值对象、领域事件、仓储模式我们用DDD做微服务拆分限界上下文划分面试官有点期待那你们项目具体怎么落地的谢飞机嗯...我们就是...建了几个包叫domain、infrastructure、application、interfaces...然后...就没有然后了。说白了就是换了个包结构还是CRUD那一套。面试官...所以你们是DDD伪落地谢飞机对就像买了健身卡就等于健身了建了DDD的包结构就等于DDD落地了面试官彻底无语好的今天的面试就到这吧。谢飞机好的好的那我什么时候能来上班面试官站起身拿起公文包你先回去等通知吧。谢飞机等等我还没说完呢我还会Docker、K8s、Elasticsearch、Netty、RPC...面试官头也不回地走出门HR下一位候选人到了吗谢飞机对着空荡荡的面试间喃喃自语我还没说我的薪资要求呢...月薪30k就行...不行25k也行啊...20k20k总可以了吧知识点详细解析小白必看以下是对面试中所有问题的详细技术解析认真看完你也能吊打面试官一、HashMap底层原理详解1.1 数据结构JDK 1.7及之前数组 链表JDK 1.8及之后数组 链表 红黑树1.2 核心参数数组容量capacity默认16必须是2的幂负载因子loadFactor默认0.75扩容阈值thresholdcapacity * loadFactor 16*0.75121.3 为什么容量是2的幂计算索引时用(n - 1) hash代替%运算效率更高。只有容量是2的幂时(n-1)的二进制才是全1才能充分利用哈希值。1.4 为什么阈值是8这是泊松分布的计算结果。在负载因子0.75的情况下链表长度达到8的概率极低约0.00000006。如果链表长度到了8说明哈希函数已经严重失效此时转红黑树可以挽救性能。1.5 put流程计算key的hash值如果数组为空先扩容计算索引位置(n - 1) hash如果该位置为空直接插入如果不为空判断key是否相等相等则覆盖如果是红黑树按红黑树插入如果是链表遍历链表如果找到相同key则覆盖否则尾部插入插入后判断链表长度是否 8如果数组长度 64先扩容 64才转红黑树插入后判断size是否 threshold是则扩容二、ArrayList vs LinkedList| 对比项 | ArrayList | LinkedList | |--------|-----------|------------| | 底层结构 | 动态数组 | 双向链表 | | 随机访问 | O(1) | O(n) | | 尾部插入 | O(1) 均摊 | O(1) | | 中间插入 | O(n) 需移动元素 | O(n) 需先查找位置 | | 内存占用 | 连续内存有预留空间 | 非连续每个节点存前后指针 | | 适用场景 | 查询多尾部插入多 | 头部插入多删除多 |注意LinkedList中间插入虽然是O(1)的链表操作但需要O(n)的时间找到插入位置所以总复杂度还是O(n)。三、 和 equals 的区别比较两个对象的内存地址是否同一个对象。对于基本类型比较的是值。equalsObject类的默认实现也是比较地址但很多类重写了该方法比如String、Integer等重写为比较内容。Integer缓存池-128到127之间的Integer对象会被缓存用valueOf()返回的是缓存中的对象。但new Integer()一定会创建新对象。Integer a 127; // 等价于 Integer.valueOf(127)从缓存取 Integer b 127; // 同样从缓存取所以 a b 为 true Integer c 128; // 超过缓存范围创建新对象 Integer d 128; // 创建新对象所以 c d 为 false四、String、StringBuffer、StringBuilder| 对比项 | String | StringBuffer | StringBuilder | |--------|--------|--------------|---------------| | 可变性 | 不可变 | 可变 | 可变 | | 线程安全 | 安全不可变天然安全 | 安全方法加synchronized | 不安全 | | 性能 | 拼接慢创建新对象 | 较慢有锁 | 最快 | | 使用场景 | 少量字符串操作 | 多线程共享变量拼接 | 单线程大量拼接 |五、volatile 关键字5.1 两大特性可见性一个线程修改volatile变量其他线程立即可见。底层通过缓存一致性协议MESI实现。禁止指令重排序通过内存屏障实现。5.2 内存屏障写操作写前插入StoreStore屏障写后插入StoreLoad屏障读操作读前插入LoadLoad屏障读后插入LoadStore屏障5.3 不保证原子性volatile不能保证原子性比如count不是原子操作。解决方案使用AtomicIntegerCAS使用synchronized使用LongAdder高并发推荐5.4 经典应用状态标志位volatile boolean flag true;DCL单例模式中的防止指令重排序六、synchronized vs ReentrantLock| 对比项 | synchronized | ReentrantLock | |--------|-------------|---------------| | 实现层次 | JVM关键字 | API层面Java类 | | 加锁解锁 | 自动 | 手动lock/unlock | | 公平性 | 非公平 | 可设置公平/非公平 | | 可中断性 | 不支持 | 支持lockInterruptibly() | | 条件等待 | 只能notify/notifyAll | 可创建多个Condition | | 锁状态 | 不可判断 | 可通过tryLock()判断 | | 底层原理 | 对象头Mark Word Monitor | AQSAbstractQueuedSynchronizer |JDK 1.6 synchronized优化偏向锁只有一个线程访问时偏向该线程轻量级锁少量线程竞争时自旋获取重量级锁大量线程竞争时阻塞等待锁升级偏向锁 → 轻量级锁 → 重量级锁不可逆七、线程池核心参数ThreadPoolExecutor executor new ThreadPoolExecutor( corePoolSize, // 核心线程数 maximumPoolSize, // 最大线程数 keepAliveTime, // 空闲线程存活时间 TimeUnit.SECONDS, // 时间单位 workQueue, // 任务队列 threadFactory, // 线程工厂 handler // 拒绝策略 );7.1 执行流程线程数 corePoolSize创建核心线程执行线程数 corePoolSize放入workQueueworkQueue满了 且 线程数 maximumPoolSize创建临时线程workQueue满了 且 线程数 maximumPoolSize执行拒绝策略7.2 4种拒绝策略| 策略 | 说明 | |------|------| | AbortPolicy | 抛RejectedExecutionException默认 | | CallerRunsPolicy | 调用者线程自己执行 | | DiscardPolicy | 直接丢弃不抛异常 | | DiscardOldestPolicy | 丢弃队列中最早的任务然后重新提交 |7.3 如何合理设置线程数CPU密集型N1N为CPU核数IO密集型2N或N / (1 - 阻塞系数)八、Spring IOC 和 AOP8.1 IOC控制反转概念将对象的创建和管理权从程序代码转移到Spring容器DI依赖注入IOC的具体实现方式注入方式构造器注入推荐、setter注入、字段注入8.2 AOP面向切面编程核心概念切面Aspect、通知Advice、切点Pointcut、连接点JoinPoint通知类型Before、After、Around、AfterReturning、AfterThrowing底层实现JDK动态代理有接口和CGLIB代理无接口应用场景日志记录、权限校验、事务管理、性能监控九、Redis缓存三大问题9.1 缓存穿透现象查询不存在的数据请求直达数据库解决方案缓存空值设置较短过期时间布隆过滤器判断key是否存在9.2 缓存击穿现象热点key过期大量并发请求直达数据库解决方案互斥锁setnx热点key永不过期后台异步刷新9.3 缓存雪崩现象大量key同时过期 或 Redis宕机解决方案过期时间加随机值Redis集群主从哨兵/Cluster本地缓存Caffeine 限流降级十、MySQL索引失效场景对索引列使用函数WHERE SUBSTR(name,1,3)abc隐式类型转换字段是int传字符串WHERE id123不符合最左前缀原则联合索引(a,b,c)查询只用了b和clike以%开头WHERE name LIKE %abcOR条件不全是索引列WHERE a1 OR b2b不是索引数据量极小MySQL认为全表扫描更快索引列参与计算WHERE a1100使用不等于WHERE a ! 1IS NOT NULL某些情况下十一、RabbitMQ消息可靠性11.1 消息丢失的三种场景| 阶段 | 解决方案 | |------|----------| | 生产者 → Broker | Confirm机制 重试 | | Broker自身 | 持久化交换机、队列、消息 镜像队列 | | Broker → 消费者 | 手动ACK 幂等性处理 |11.2 消息幂等性数据库唯一索引Redis存消息IDsetnx业务逻辑本身幂等如状态机十二、DDD领域驱动设计核心概念实体Entity有唯一标识可变的业务对象值对象Value Object无唯一标识不可变的属性集合聚合Aggregate一组相关对象的集合包含聚合根聚合根Aggregate Root聚合的入口外部只能通过聚合根操作聚合内部领域事件Domain Event领域内发生的重要事件限界上下文Bounded Context领域的边界每个限界上下文有独立的通用语言仓储Repository聚合的存储和检索接口DDD四层架构Interface接口层接收请求返回响应Application应用层编排业务不包含业务逻辑Domain领域层核心业务逻辑Infrastructure基础设施层技术实现数据库、MQ等写在最后谢飞机的故事虽然搞笑但背后反映了很多程序员知其然不知其所以然的通病。面试官让回去等通知其实就是委婉的拒绝。所以各位小伙伴们看完这篇文章赶紧把上面的知识点吃透下次面试可别像谢飞机一样翻车啦记住面试不是背答案而是要真正理解原理。只有把知识内化成自己的才能在面试中游刃有余祝大家都能拿到心仪的Offer加油