面试官:Java线程池的核心参数了解吗?谢飞机:会用new Thread就不错了!
面试官:请坐。看你简历上写了熟悉Java并发编程,那我们先聊聊线程池。
谢飞机:没问题,我天天用!new Thread().start(),稳得一批!
面试官:……我们说的不是这个。我说的是ThreadPoolExecutor。
谢飞机:哦!那个啊,用过用过,Executors.newFixedThreadPool(5),五条龙,火力全开!
面试官(扶额):那你来说说,线程池的核心参数有哪些?
谢飞机:核心参数?是不是 coreSize、maxSize、queueSize?
面试官:接近了,准确说是:corePoolSize、maximumPoolSize、workQueue、keepAliveTime、unit、threadFactory、handler。七个。
谢飞机:哎哟这么多,记不住啊。但我知道,任务来了先塞核心线程,满了就进队列,队列满了再开新线程,最多到最大线程数,再不行就拒绝。
面试官:嗯,这逻辑还行。那如果我设置 corePoolSize=2,maximumPoolSize=5,队列是 LinkedBlockingQueue(无界),会发生什么?
谢飞机:那不就是最多5个线程干活?
面试官:错。无界队列下,maximumPoolSize 是无效的。永远不会有超过 corePoolSize 的线程被创建。
谢飞机:啊?那我还怎么扩容?
面试官:这就是为什么阿里开发手册禁止使用 Executors 创建线程池,推荐手动 new ThreadPoolExecutor,避免资源耗尽。
面试官:接下来聊聊 ConcurrentHashMap。
谢飞机:这个我熟!HashMap 线程不安全,Concurrent 就安全了,加了 synchronized 吧?
面试官:JDK 1.7 是分段锁,1.8 改成什么了?
谢飞机:改成……synchronized + CAS?
面试官:对了一半。1.8 使用 synchronized 锁住链表头或红黑树根节点,配合 CAS 操作,减少了锁粒度。
谢飞机:那扩容呢?
面试官:1.8 扩容是并发进行的,通过 transfer 方法,每个线程负责迁移一部分桶。
谢飞机:那多个线程同时 put 呢?
面试官:不同桶之间互不影响,只有哈希冲突时才可能竞争同一个桶的锁。
谢飞机:明白了,就像食堂打饭,每人一个窗口,互不打架。
面试官:比喻勉强及格。
面试官:最后问个 Spring。
谢飞机:Spring 我闭着眼都能写!@Controller、@Service、@Autowired,三连击!
面试官:Bean 的作用域有哪些?
谢飞机:singleton 和 prototype!默认是单例。
面试官:还有呢?
谢飞机:request、session……还有 application?
面试官:对。那 Bean 的生命周期你知道吗?
谢飞机:new 出来,然后初始化,然后用,最后销毁。
面试官:具体点?
谢飞机:实现 InitializingBean 接口可以做初始化,或者用 @PostConstruct 注解……销毁用 DisposableBean 或 @PreDestroy。
面试官:不错。那循环依赖怎么解决的?
谢飞机:三级缓存!一级缓存放成品 Bean,二级放半成品,三级放 ObjectFactory。
面试官:如果 A 依赖 B,B 依赖 A,都是构造器注入,能解决吗?
谢飞机:能……吧?
面试官:不能!构造器注入会导致提前暴露对象失败,三级缓存也救不了。只能用设值注入。
谢飞机:哦……学到了。
面试官:今天就到这里,你的基础还算可以,但深度不够。回去等通知吧。
谢飞机:好嘞,等您电话!
参考答案详解
1. 线程池核心参数
ThreadPoolExecutor 七大参数:
- corePoolSize:核心线程数,即使空闲也不会被回收(除非设置 allowCoreThreadTimeOut)。
- maximumPoolSize:最大线程数。
- workQueue:阻塞队列,用于存放待执行任务。
- keepAliveTime:非核心线程空闲存活时间。
- unit:时间单位。
- threadFactory:线程创建工厂,可用于自定义线程名称等。
- handler:拒绝策略,如 AbortPolicy、CallerRunsPolicy 等。
执行流程:
- 任务提交,若当前线程数 < corePoolSize,创建新线程执行。
- 若 ≥ corePoolSize,任务加入队列。
- 若队列满,且线程数 < maximumPoolSize,创建新线程执行。
- 若线程数已达 maximumPoolSize 且队列满,执行拒绝策略。
无界队列陷阱:如 LinkedBlockingQueue 无指定容量,则为 Integer.MAX_VALUE,导致队列几乎不会满,maximumPoolSize 失效,可能导致 OOM。
2. ConcurrentHashMap
- JDK 1.7:采用分段锁(Segment),每个 Segment 相当于一个 HashMap,锁粒度为 Segment。
- JDK 1.8:放弃 Segment,采用
synchronized+CAS。Node 数组每个桶的头节点作为锁,锁粒度更细。 - put 流程:计算位置 → CAS 设置头节点 → 冲突则遍历链表/红黑树插入 → 链表转红黑树阈值为 8。
- 扩容:
transfer方法,多线程协作迁移,通过stride划分任务块。
3. Spring Bean 生命周期
- 实例化(new)
- 属性赋值(populate)
- 初始化前(@PostConstruct、InitializingBean.beforePropertiesSet)
- 初始化后(BeanPostProcessor.postProcessAfterInitialization)
- 使用
- 销毁前(@PreDestroy、DisposableBean.destroy)
- 销毁
循环依赖:
- Spring 通过三级缓存解决设值注入的循环依赖:
- 一级:singletonObjects(成品)
- 二级:earlySingletonObjects(早期暴露的半成品)
- 三级:singletonFactories(ObjectFactory,用于创建早期引用)
- 构造器注入无法解决,因为对象尚未创建完成,无法提前暴露。