Java面试故事:谢飞机的互联网大厂面试之旅
第一轮:基础提问
面试官:我们先从简单的问题开始吧,讲讲HashMap是线程安全的吗?
谢飞机:肯定不是吧,我知道HashMap不是线程安全的。不过线程安全可以用ConcurrentHashMap!
面试官:不错,知道问题所在还给出解决方法,回答得很到位。那你觉得HashMap的默认初始容量是多少?载满后如何扩容呢?
谢飞机:哦,这个好像是16吧,扩容是……是两倍吧?
面试官:基本正确。那你知道HashMap扩容会带来什么问题吗?
谢飞机:呃,这个……是不是会变慢?嗯,我觉得性能会受影响吧。
面试官:好,那这个问题我们之后再详细探讨。
第二轮:进阶问题
面试官:下一步,我们谈谈多线程的问题吧。你觉得线程池的核心线程数应该怎么设置?
谢飞机:嗯……就……越多越好吧,这样线程不就更快嘛?
面试官:这个问题还有待完善答案。再告诉我,ThreadPoolExecutor的拒绝策略有几种?
谢飞机:呃,拒绝策略?嗯,是不是可以抛异常?别的我不太清楚。
面试官:拒绝策略四种,其中AbortPolicy确实会抛异常。OK,再问你个简单点的,线程池的作用是什么?
谢飞机:这个我知道!线程池可以复用线程,减少频繁创建和销毁线程的开销!
面试官:回答不错!
第三轮:高阶应用
面试官:我们假设在实际项目中,你需要处理一个高QPS场景,如何通过Redis来减少数据库的压力?
谢飞机:嗯……加缓存?就是查数据库之前先去Redis里看看?
面试官:对对,这是最基础的缓存思路,那再深挖一下,如果缓存击穿、穿透或者雪崩了,该如何优化?
谢飞机:呃……呃……缓存击穿是不是加个锁?穿透是不是用布隆过滤器?雪崩……那就多加几个服务器吧?
面试官:思路没问题,但还可以更细化。最后一个问题,Redis的数据淘汰策略有哪些?
谢飞机:那个,按……按照什么LRU、TTL啥的?
面试官:基本正确。
面试官沉思片刻,整理文件。
面试官:今天就到这里吧,谢飞机同学,回去等通知。
谢飞机心里一喜,以为春天到了……
答案详解
1.HashMap是否线程安全及其扩容问题
HashMap并不是线程安全的,它可以在多线程环境下导致数据一致性问题。可以使用ConcurrentHashMap进行替代。- 默认初始容量为16,扩容后容量会变为原来的两倍。
- 扩容时会重新分配所有元素,如果在多线程环境下,会引发环形链表导致死循环的问题。
2. 线程池相关问题
- 核心线程数的设置应基于任务的CPU密集型或IO密集型,比如
CPU核数 + 1适用于计算密集型场景。 ThreadPoolExecutor有四种拒绝策略:AbortPolicy(抛异常)、CallerRunsPolicy(调用者线程处理任务)、DiscardPolicy(直接丢弃任务)和DiscardOldestPolicy(丢弃最旧的任务)。- 线程池的作用是复用线程,减少资源消耗,同时便于管理。
3.Redis优化高QPS场景
- 缓存优先查询:查询数据库前,先查
Redis。- 缓存击穿:对热点Key加
mutex或互斥锁。 - 缓存穿透:使用布隆过滤器来避免无效请求。
- 缓存雪崩:设置随机失效时间,或采用多层缓存结构。
- 缓存击穿:对热点Key加
- 淘汰策略包括:
- volatile-lru:最近最少使用。
- volatile-ttl:即将过期的优先删除。
- allkeys-lfu:最不常使用。
- noeviction:拒绝写入新数据。
- 使用时需根据业务需求合理选择。
通过这次对话形式的面试,希望能帮助初学者更直接地了解面试涉及的技术点,并循序渐进地掌握。