面试官:Java线程池的核心参数了解吗?谢飞机:会用new Thread就不错了!

47 阅读4分钟

面试官: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 等。

执行流程

  1. 任务提交,若当前线程数 < corePoolSize,创建新线程执行。
  2. 若 ≥ corePoolSize,任务加入队列。
  3. 若队列满,且线程数 < maximumPoolSize,创建新线程执行。
  4. 若线程数已达 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 生命周期

  1. 实例化(new)
  2. 属性赋值(populate)
  3. 初始化前(@PostConstruct、InitializingBean.beforePropertiesSet)
  4. 初始化后(BeanPostProcessor.postProcessAfterInitialization)
  5. 使用
  6. 销毁前(@PreDestroy、DisposableBean.destroy)
  7. 销毁

循环依赖

  • Spring 通过三级缓存解决设值注入的循环依赖:
    • 一级:singletonObjects(成品)
    • 二级:earlySingletonObjects(早期暴露的半成品)
    • 三级:singletonFactories(ObjectFactory,用于创建早期引用)
  • 构造器注入无法解决,因为对象尚未创建完成,无法提前暴露。