前阵子,一个朋友打电话给我,电话一接通,他就像打了鸡血一样劈头盖脸:
“小米!你不是搞 Java 的么?我刚面完某东,一道题把我问懵了!”
“什么题?”我一边搅着咖啡,一边打开电脑准备陪他复盘。
“面试官问我:说说 Executor 框架,你们项目中用过吗?为啥不用 new Thread?我脑袋一热就说了个线程池,然后……然后就没然后了。”
我笑了笑,说:
“Executor 框架是个宝藏啊兄弟,这可是并发编程的门面担当,得好好了解了解。”
于是,这篇文章就此诞生,今天我们就聊聊——什么是 Executor 框架?为什么使用 Executor 框架?
背景故事:从一个线程开启说起让我们回到最初学 Java 的日子,那时写多线程的方式是这样的:
这没问题,能跑,能并发,甚至能让人觉得“哇,我都用上多线程了,我好牛”。
但随着项目越做越大,线程一多,问题就来了:
每次都 new Thread 性能差,线程创建和销毁开销大;
无法控制线程数量,CPU 被打爆;
线程的生命周期混乱,难以管理;
无法方便地重用线程;
没有任务调度、没有统一的异常处理……
于是,Java 在 JDK 1.5 推出了一个神器——Executor 框架,也就是咱们今天的主角。
什么是 Executor 框架?用一句话说:
Executor 框架是 Java 提供的一套用于并发任务管理的线程池框架。
它的核心理念是任务提交与任务执行的解耦。
用个比喻:
你去打印店打印文件,把文件交给店员(Executor),
店员把它安排给打印机(线程)处理,
你不需要知道哪个打印机在打、什么时候打完,
你只要管交活就行了。
而不是你自己上去操作打印机,那多乱。
核心接口一览Executor 框架不是一个类,而是一套接口+实现类的组合:
我们最常用的其实是 ExecutorService 接口,比如:
这是面试最常问的点,咱来展开说说。
1、提升性能,复用线程
每次 new Thread 都会创建一个新线程,频繁创建/销毁线程开销大、慢、占资源。
Executor 框架使用线程池技术,线程创建一次就可以反复利用。
比如一个固定大小的线程池,最多只开 5 个线程,任务再多也不会炸:
提升性能,同时保护资源。
2、解耦业务与线程管理
传统 Thread 写法,业务代码和线程管理混在一起。
Executor 让我们只关注“要干啥”,而不是“谁来干、怎么干”。
3、支持任务调度
Executor 框架提供 ScheduledExecutorService,可以做定时任务:
是不是感觉像是个升级版的 Timer?
4、异常处理能力强
ExecutorService 返回 Future,可以捕获异常:
而 new Thread 的话,如果线程抛异常,主线程根本感知不到。
5、可扩展、可监控、可调优
使用 ThreadPoolExecutor 我们可以:
设置核心线程数、最大线程数
设置任务队列大小
自定义拒绝策略(任务太多时该咋办)
实现线程池监控(运行中线程数、任务数、队列长度等)
生产环境中,一般都推荐使用这种方式自己构建线程池,而不是直接用 Executors 工具类。
为什么?往下看!
Executors 工具类的“坑”Executors.newFixedThreadPool 是不是很香?香,但也有坑!
比如:
背后使用的是 LinkedBlockingQueue,默认队列长度是 Integer.MAX_VALUE,相当于无限大。
这会导致:
短时间内任务激增,队列撑爆内存
没有合理的拒绝策略,系统崩溃
所以,阿里巴巴 Java 开发手册里明确指出:
不允许使用 Executors 创建线程池,要通过 ThreadPoolExecutor 显式定义。
自定义线程池推荐写法四种常用的拒绝策略:
Executor 框架的本质是:
用线程池来管理任务的执行,让线程的使用更可控、更高效、更优雅!
小米的面试小建议最后,用我那位面试翻车朋友的故事来收尾:
他后来认真复盘了 Executor 框架,再次面试,面试官问起线程池,他不仅答出了 Executor 的用法,还讲了线程池参数调优、拒绝策略、异常处理,最后成功拿下 Offer!
所以——
别小看“Executor 框架”这个题,它考的是你的并发理解、项目经验、代码习惯,甚至你的工程意识。
END如果你看到这里,说明你已经对 Executor 框架有了一定的掌握!
欢迎转发给你正在找工作的朋友,也欢迎留言告诉我:你在面试中还遇到了哪些 Java 并发相关的问题?我们下次接着聊!