第一轮:线程与线程池的初探
面试官:请介绍一下Java中创建线程的几种方式?
谢飞机:我知道!有继承Thread类、实现Runnable接口,还有Callable+Future,对吧?我还知道lambda可以简化写法。
面试官(点头):不错,基础扎实。那我们深入一点——线程池你用过吗?
谢飞机:当然!我天天用new Thread(),比线程池快多了,还不占内存!
面试官(皱眉):……那你听说过Executors工具类吗?
谢飞机:听说过!它能帮我们execute()线程,就像执行命令一样!
面试官:那你用过ThreadPoolExecutor吗?它的核心参数有哪些?
谢飞机:嗯……corePoolSize、maximumPoolSize、keepAliveTime、workQueue,还有……还有一个叫什么来着?哦对,threadFactory和handler!我背过八股文!
面试官(略感欣慰):还行。那如果核心线程数满了,新任务会怎么处理?
谢飞机:直接开新线程,直到最大线程数,再不行就丢掉任务!简单粗暴,高效!
第二轮:并发容器与Spring Bean
面试官:既然提到并发,ConcurrentHashMap和HashMap有什么区别?
谢飞机:HashMap不是线程安全的,ConcurrentHashMap是线程安全的!我项目里都用后者,稳如老狗!
面试官:JDK1.8之后它是怎么保证线程安全的?
谢飞机:加了synchronized锁整个table?不对……好像是每个链表头加锁?哦!是CAS + synchronized!
面试官:很好。那Spring中Bean的作用域有哪些?
谢飞机:singleton、prototype,还有request、session……我一般只用单例。
面试官:如果一个prototype的Bean注入到一个singleton的Bean中,会出现什么问题?
谢飞机:呃……内存泄漏?不不不,应该是每次都能拿到新的prototype实例!因为Spring很聪明!
面试官(扶额):实际上默认情况下,只会注入一次。要解决这个问题,可以用@Lookup或者代理模式。
第三轮:分布式与中间件
面试官:你们项目用Redis做缓存,那缓存穿透怎么解决?
谢飞机:加个if判断,如果为空就不查数据库!完美规避!
面试官:如果恶意请求一直查不存在的key呢?
谢飞机:那就让它穿透呗,反正数据库扛得住!
面试官:……我们通常用布隆过滤器或缓存空值来应对。
面试官:最后一个问题,Dubbo的负载均衡策略有哪些?
谢飞机:随机、轮询、最少活跃调用数,还有……哈希一致性!我全都会!
面试官:很好。今天的面试就到这里,你的表现……很有特色。回去等通知吧。
谢飞机:谢谢!我回去就把new Thread删了,改用线程池!
答案详解
1. ThreadPoolExecutor 核心参数
- corePoolSize:核心线程数,即使空闲也不会被回收(除非设置allowCoreThreadTimeOut)。
- maximumPoolSize:最大线程数,线程池允许创建的最大线程数量。
- keepAliveTime:非核心线程空闲时的存活时间。
- workQueue:任务队列,如ArrayBlockingQueue、LinkedBlockingQueue等。
- threadFactory:创建线程的工厂,可用于自定义线程名称等。
- handler:拒绝策略,如AbortPolicy、CallerRunsPolicy等。
任务提交流程:
- 当前运行线程 < corePoolSize → 创建新线程执行任务。
- ≥ corePoolSize → 添加到队列。
- 队列满 → 创建新线程直到 maximumPoolSize。
- 达到 maximumPoolSize 且队列满 → 执行拒绝策略。
2. ConcurrentHashMap 实现原理(JDK1.8)
- 使用 CAS + synchronized 锁住链表头节点或红黑树根节点。
- 分段锁机制被取消,改为更细粒度的桶级别锁。
- put操作:计算hash → 找到桶位置 → CAS尝试插入 → 冲突则synchronized同步添加。
3. Spring Bean 作用域
- singleton:容器中只有一个实例,全局共享。
- prototype:每次获取都创建新实例。
- request/session/application:Web环境下对应生命周期。
注意:singleton Bean 注入 prototype Bean 时,默认只注入一次。解决方案:
- 使用
@Scope(proxyMode = ScopedProxyMode.TARGET_CLASS)创建代理。- 使用
@Lookup注解方法让容器每次返回新实例。
4. 缓存穿透解决方案
- 缓存空值:查询结果为空也缓存,设置较短过期时间。
- 布隆过滤器:在访问缓存前判断key是否存在,避免无效查询击穿数据库。
- 限流降级:防止恶意攻击导致系统崩溃。
5. Dubbo 负载均衡策略
- RandomLoadBalance:按权重随机选择(默认)。
- RoundRobinLoadBalance:轮询。
- LeastActiveLoadBalance:优先选择活跃调用数最少的提供者。
- ConsistentHashLoadBalance:相同参数的请求总是发到同一台机器,适用于缓存场景。
本文通过幽默对话形式讲解技术要点,帮助读者轻松掌握Java面试核心知识。