一位大厂面试官的灵魂发问:Executor 和 Executors 有什么区别?

B站影视 韩国电影 2025-04-10 17:53 2

摘要:这句话,是我在一次 Java 社招面试时,被面试官笑着抛出来的第一道题。那一刻,我脑子里虽然有印象,但也没准备好“完美回答”。今天,作为一个从社招“坑坑洼洼”中走过来的老程序员,我想用一个轻松的小故事,和你聊聊这道经典的面试题——希望你看完后,再遇到这题,能胸

“你知道 Executor 和 Executors 的区别吗?”

这句话,是我在一次 Java 社招面试时,被面试官笑着抛出来的第一道题。那一刻,我脑子里虽然有印象,但也没准备好“完美回答”。今天,作为一个从社招“坑坑洼洼”中走过来的老程序员,我想用一个轻松的小故事,和你聊聊这道经典的面试题——希望你看完后,再遇到这题,能胸有成竹地笑着说:“这个,我熟。”

时间回到两年前。那时候的我,已经在一家中型互联网公司干了五年。代码写了不少,也踩了不少坑。可当真正要面试大厂的时候,我才发现,自己对很多基础知识,理解得还不够深入。

那次社招面试的场景我到现在还记得清清楚楚:

面试官微笑着问:“小米,线程池你应该用过吧?”

我点点头:“嗯,用得不少,Executors.newFixedThreadPool(5)我天天用。”

面试官点点头:“那你知道Executor和Executors有什么区别吗?”

我脑子一紧:这……不是差一个s的事吗?

我一边嘴硬地说“Executor 是接口,Executors 是工具类”,一边心虚地知道,这回答——太浅了。

面试官没有直接否定我,而是笑了笑说:“我们来深入聊聊。”

那天我被“教育”了四十分钟,整整四十分钟。

后来,我回家做了三天的笔记,写了两篇总结,才终于把这对“线程池的亲兄弟”搞明白。

今天,我就用讲故事的方式,把我当年那“被教育”的知识点,跟你掰开揉碎说一遍。

我们来想象一个场景:

你是一位餐厅经理(Main 线程),你要安排服务员(线程)去处理顾客订单(任务)。你不能什么都亲力亲为,你需要一个调度系统。

于是你请来了两个角色:

Executor(执行者):他是一个接口,你可以把任务交给他,由他决定怎么执行。Executors(执行者们):他是一个工厂类,可以帮你创建各种不同风格的“Executor”。Executor 是接口,定义了“线程池”的标准。Executors 是工具类,提供了静态方法来创建不同类型的线程池。

这个时候你可能会想:哦,不就是一个定义规则,一个提供实例吗?差不多该结束了吧?

不,小米摇摇头——刚刚开始。

只有一个方法,execute(Runnable command),听起来好像就是线程池的核心。

没错,它是 Java 并发包(java.util.concurrent)中的基石。任何想要被称为“执行者”的类,只要实现这个接口,就能被拿来“跑任务”。

典型的实现有:

ThreadPoolExecutor:这个是真正干活的线程池类。ScheduledThreadPoolExecutor:支持定时任务和周期任务的线程池。

Executor 提供了“面向接口编程”的基础,但他本人并不关心你怎么实现。你想直接 new 一个 Thread?OK;你想用一个线程池去维护线程复用?也OK。

再来看 Executors,这是一个工具类,里面提供了大量创建线程池的静态方法:

这些方法很方便,随手就能搞出一个线程池用来执行任务。

那你可能会想:这么方便,我为什么还要研究别的方式?

别急,问题就来了。

为什么?

我们来一一拆解:

1. Executors.newFixedThreadPool(int n)

优点:固定线程数,避免线程数量无限制增长。缺点:任务队列是无界队列(LinkedBlockingQueue),如果任务提交过多,可能导致OOM(内存溢出)。

2. Executors.newCachedThreadPool

优点:线程数不固定,任务多就创建线程。缺点:线程数无限制增长,高并发情况下可能直接把系统拖垮。

3. Executors.newSingleThreadExecutor

优点:只有一个线程,任务按顺序执行,适合一些“串行化任务”。缺点:同样使用无界队列,如果任务堆积,也可能OOM。

4. Executors.newScheduledThreadPool

用于定时或周期性任务。本身还算稳定,但使用不当一样会出问题。

现在,我们再回到那个面试现场。

如果你只是回答“Executor 是接口,Executors 是工具类”,面试官可能会点头,但心里已经给你贴了个“只懂 API,不懂设计”的标签。

正确的答题姿势是这样的:

“Executor 是一个定义了任务执行机制的接口,主要方法是 execute(Runnable)。而 Executors 是一个工具类,提供了一系列静态方法,帮助我们快速创建不同类型的线程池。”

“不过,在实际生产中,并不推荐直接使用 Executors 提供的工厂方法,因为它们背后隐藏了一些不易察觉的风险,例如 CachedThreadPool 和 FixedThreadPool 默认使用无界队列或无限线程,容易导致 OOM 或线程爆炸。”

“更推荐我们自己使用 ThreadPoolExecutor 构造函数,明确设置核心线程数、最大线程数、队列大小和拒绝策略,来精细控制线程池行为。”

这才是让面试官眼前一亮的回答。

推荐写法:

参数解释:

核心线程数:保留的线程数,长期存活。最大线程数:并发高峰时,线程可以临时扩展到这个数量。空闲线程保活时间:超过 core 的线程,在空闲多久后被回收。队列:任务等待的地方,设置有界避免堆积。拒绝策略:任务超载时怎么办?Abort(抛异常)、CallerRuns(当前线程执行)、Discard(丢弃)、DiscardOldest(丢最早的)。

那次面试,我虽然在“Executor vs Executors”这一题没拿满分,但却在复盘中成长巨大。

很多时候,不是我们不会,而是没“透过源码看本质”

最后,我用一句话来总结本文,也送给你:

“Executors 让你写得快,ThreadPoolExecutor 让你跑得稳。”

别再让“无界队列”、“线程爆炸”、“OOM”等等这些坑在生产中炸锅。写代码,不只是能跑,而是能跑得久、跑得稳。

来源:山东王者学伴

相关推荐