你有没有被"线程池"打过脸?
先说个故事吧。
上个月我去面一家知名大厂,二面官看起来特别和蔼,就像楼下便利店大叔那种感觉。我本以为这一面轻轻松松,没想到......
面试官:你了解 Java 的线程池吗?
我:嗯嗯,知道的,用 Executors 创建线程池嘛,挺方便的,比如 Executors.newFixedThreadPool(10) 这种。
面试官点点头,露出诡异的微笑:那你知道为啥不推荐用 Executors 吗?
我:啊......?不是挺好用的吗?这......
空气突然安静。
我努力回忆当年看的 Java 并发编程艺术,结果脑袋一片空白,支支吾吾地说了几句含糊其辞的话,眼看着面试官的眉头越皱越紧。
面试官叹了口气,说:"没事,这个问题很多人都答不上来。"
当时我就一个念头:
线程池啊线程池,你这是给我上了一课!
于是,面试回来我连夜查文档、翻源码、画图、写博客,发誓下次再被问到这个问题,我一定把它讲得明明白白,像讲故事一样讲给别人听!
今天,就让我把这个故事讲完,也希望你不要像当初的我一样,被这道"Executors 和 ThreadPoolExecutor 的区别"给绊倒。
Executors 是糖,ThreadPoolExecutor 是本体
1. Executors 是啥?
Executors 是 Java 提供的一个工具类 ,里面提供了一堆静态方法,可以帮你方便快捷地创建各种线程池。
比如这些:
这些方法看起来是不是很优雅?的确,好看,好用,好记,用它可以一行代码创建线程池,谁不爱呢?
但,这也埋下了一个雷......
2. ThreadPoolExecutor 才是真正的线程池实现
ThreadPoolExecutor 是 java.util.concurrent 包下的核心类,它才是线程池的真正实现者。Executors 内部就是调用了它来构造不同类型的线程池。
我们来看一个手动创建线程池的例子:
这个构造方法虽然啰嗦,但每一个参数你都可以控制! 这就是和 Executors 最大的区别!
用 Executors 是"糖衣炮弹",吃起来爽,背后全是坑。
为什么说 Executors 不推荐用了?
现在你可能会问,Executors 不是封装好的吗?为啥还说它是"坑"?
原因就三个字:
不可控!
1. Executors.newFixedThreadPool()
它底层用的是:
重点来了:它用了一个无限长的阻塞队列LinkedBlockingQueue!
这意味着啥?
- 如果你提交了成千上万个任务,线程池不扩容,任务全都堆在队列里,可能会导致内存暴涨、OOM(Out Of Memory) !
简直是温水煮青蛙!
2. Executors.newCachedThreadPool()
它底层是这样的:
看出来了吗?
- 核心线程数是 0
- 最大线程数是 Integer.MAX_VALUE(20 多亿!)
- 队列是 SynchronousQueue(不会缓存任务)
这意味着:
- 你每提交一个任务,它就会尝试新建线程,直到撑爆你的 CPU 或内存!
所以,不加限制地用 CachedThreadPool,风险极大。
3. Executors.newSingleThreadExecutor()
同样的,底层也用了无限长的 LinkedBlockingQueue。
一旦处理不过来,任务又堆积如山。
4、总结一下
所以,阿里巴巴的《Java开发手册》中明确写到:
- 线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式,这样规避资源耗尽的风险。
正确姿势:手动创建线程池
那我们应该怎么做?
老老实实用 ThreadPoolExecutor,自己掌控线程数和队列容量。
比如一个推荐的线程池创建方式:
这样,你能控制最大线程数量、队列容量、线程回收时间、拒绝策略,一切尽在掌握中。
面试官到底想听你说什么?
你以为面试官问你"Executors 和 ThreadPoolExecutor 有什么区别",是想听你背 API 吗?
No!他想听的是你是否理解背后的风险,是否有工程实践意识。
如果你能这样回答他:
"虽然 Executors 提供了便捷的方法,但由于它默认采用无限队列或无限线程,容易引发内存溢出或系统负载问题,因此我们推荐使用 ThreadPoolExecutor 并手动设置参数,避免系统崩溃。"
你说完这段,面试官多半会点头微笑,可能还会多给你一些面试分。
常见线程池参数详解(收藏版)
再来一个彩蛋:线程池调优建议
- 对于高并发但执行时间短的任务,使用 FixedThreadPool 配有限长队列;
- 对于执行时间长的任务,应控制线程数,避免占满 CPU;
- 对于I/O密集型任务,可以设置核心线程略高于 CPU 核心数;
- 别忘了设置合理的拒绝策略,比如 CallerRunsPolicy 能在任务过载时回退到调用者线程执行。
最后,小米的面试反杀记
就在上周,我又去面了一家大厂。
这次,面试官又问:
"你说说 Executors 和 ThreadPoolExecutor 的区别。"
我一笑,娓娓道来,从源码到风险,再到推荐方案,说得对方频频点头。
最终,HR 面说:
"你这回答比我们内部文档都清楚。"
我终于体会到:
被一道题打败,不是终点;弄懂它,讲出来,就是你进阶的开始。
END
我是小米,感谢你的阅读。
如果你喜欢这种"讲故事 + 技术干货"的文章风格,欢迎点个 赞,或者分享给你身边也在准备面试的小伙伴~
下期我打算写:Future、CompletableFuture 和线程池的终极关系图解,你感兴趣吗?
我是小米,一个喜欢分享技术的31岁程序员。如果你喜欢我的文章,欢迎关注我的微信公众号"软件求生",获取更多技术干货!