文章目录

📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌
📙 作者: 编程技术圈(哇哥面试陪跑)
👉 欢迎关注、分享、评论
✔️ 持续分享更多干货内容
🌐🌏🌎➕tcmeta, 欢迎沟通交流
📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌
零、引入

王二的脸涨得像煮熟的虾,手里攥着鼠标,屏幕上的日志红得刺眼 —— 他写的商品秒杀代码,一压测就崩,满屏都是ThreadDeath和OutOfMemoryError。领导站在身后,鼻孔里的气都快喷到他后颈:“你这代码里的线程,比菜市场的苍蝇还乱,谁教你这么 new Thread 的?”
王二缩着脖子,想说 “网上都这么写”,话到嘴边又咽了回去。隔壁工位的哇哥把烟蒂摁灭在烟灰缸里,慢悠悠开口:“这不能怪他,多数菜鸟都把线程当散兵游勇,没见过正规军的章法。” 他顿了顿,指了指王二的屏幕,“Executor 框架早把线程管得明明白白,你偏不用,这不是自讨苦吃?”
点赞 + 关注,跟着哇哥和王二,用工厂管工人的道理,把 Executor 框架嚼碎了咽下去,下次再写并发代码,领导都得高看你两眼!

一、王二的 “线程蛊”:new Thread 的三大罪状

王二的代码是典型的 “菜鸟写法”—— 每次用户下单,就 new 一个 Thread 去处理库存,代码长这样:
package cn.tcmeta.threads;
import java.util.concurrent.TimeUnit;
/**
* @author: laoren
* @description: 王二的坑:new Thread满天飞,线程乱成一锅粥
* @version: 1.0.0
*/
public class ThreadDisasterSample {
// 模拟秒杀减库存
private static void reduceStock(String productId) {
try {
// 模拟库存操作耗时
TimeUnit.MILLISECONDS.sleep(50);
System.out.println(Thread.currentThread().getName() + ":商品" + productId + "库存减1");
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
static void main() {
// 模拟100个秒杀请求
for (int i = 0; i < 100; i++) {
String productId = "P" + i;
// 每次请求都new一个Thread,灾难的开始
new Thread(() -> reduceStock(productId), "Thread-" + i).start();
}
}
}

运行起来,日志里的线程名杂乱无章,压测到 1000 并发直接 OOM——JVM 里的线程比头发还多,内存扛不住了。
哇哥看了直摇头:“这代码犯了三个死罪,跟三十年代工厂里的散工一样,没章法:
- 线程复用差:new 一个 Thread 就占 1MB 左右内存,1000 个就是 1GB,内存直接炸;
- 管理混乱:线程什么时候启动、结束,全是野的,出了问题连日志都查不清;
- 任务与线程耦合:你既要写‘减库存’的任务,又要管‘线程’的生命周期,精力全花在杂事上。”
王二挠头:“那咋办?总不能不用线程吧?”
“用 Executor 框架,” 哇哥把茶杯往桌上一墩,“它就像工厂的工头,你只需要把‘减库存’的任务交给他,线程怎么开、怎么复用、怎么关,全不用你管 —— 这叫‘任务与线程解耦’,懂?”
二、用 “工厂管工人” 讲透 Executor 核心:解耦才是王道

哇哥拉过王二的草稿纸,画了个简陋的工厂示意图:
“你看,工厂里的工人(线程)、要做的零件(任务)、管工人的工头(Executor),三者各司其职。你之前的代码,是自己又当工人又当工头,累死还干不好。Executor 就是这个工头,你只需要把零件交给他,他来安排工人干活。”
✅ Executor 框架核心:一套 “管线程的规矩”
Executor 框架不是一个类,是一套接口和实现类的总称,核心是 “解耦任务提交和线程管理”。

📢 核心接口详解(王二记在烟盒上)
- Executor:最顶层,只有一个方法execute(Runnable command)—— 把任务交出去,不管怎么执行;
- ExecutorService:扩展了 Executor,加了线程池的生命周期管理,比如submit()(提交任务带返回值)、shutdown()(关闭线程池);
- ThreadPoolExecutor:核心实现类,真正的 “工头”,管理线程池的创建、任务分配、线程回收;
- Executors:工具类,帮你快速创建线程池(但生产环境要慎用,后面讲坑)。
“简单说,Executor 是‘我要干活’,ExecutorService 是‘我要干活,还要知道活干没干完’,ThreadPoolExecutor 是‘我来安排人干活,保证干得又快又好’,” 哇哥总结,“你之前的代码,连‘工头’都没有,工人自然乱成一团。”
三、改造代码:ExecutorService 让线程 “收编归队”
哇哥手把手教王二改代码,把 new Thread 换成 ExecutorService,只用了几行,代码就清爽了:
package cn.tcmeta.threads;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
// 优化版:用ExecutorService管理线程,线程池来干活
public class ExecutorRescue {
// 模拟秒杀减库存(任务不变)
private static void reduceStock(String productId) {
try {
TimeUnit.MILLISECONDS.sleep(50);
System.out.println(Thread.currentThread().getName() + ":商品" + productId + "库存减1");
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
public static void main(String[] args) throws InterruptedException {
// 1. 创建固定线程池:10个线程(工头带10个工人)
ExecutorService executorService = Executors.newFixedThreadPool(10);
// 2. 提交100个任务(100个零件交给工头)
for (int i = 0; i < 100; i++) {
String productId = "P" + i;
// 提交任务,不用管线程怎么开
executorService.submit(() -> reduceStock(productId));
}
// 3. 关闭线程池:先停止接收新任务,等已提交的任务完成
executorService.shutdown();
// 等待所有任务完成,最多等1分钟
if (!executorService.awaitTermination(1, TimeUnit.MINUTES)) {
// 没完成就强制关闭
executorService.shutdownNow();
}
System.out.println("所有秒杀任务处理完毕");
}
}

王二看着日志里重复出现的pool-1-thread-1,眼睛亮了:“线程真的复用了!之前 100 个任务开 100 个线程,现在 10 个就够了!”
“这才是 Executor 的精髓,” 哇哥点了根烟,“线程池里的线程是‘常驻军’,任务做完不销毁,等着下一个任务,省了创建销毁线程的开销 —— 就像工厂的工人,不会做完一个零件就辞职,等着下一个零件送过来。”
四、ExecutorService 常用方法:王二的 “工头指令集”
哇哥怕王二记混,把常用方法总结成 “工头的指挥手势”,贴在他显示器上:
- submit(Runnable):提交无返回值的任务,返回 Future 对象(可以判断任务是否完成);
- submit(Callable):提交有返回值的任务,Future 的 get () 方法能拿结果;
- shutdown():平缓关闭,拒绝新任务,等待已提交任务完成;
- shutdownNow():强制关闭,中断正在执行的任务,返回未执行的任务;
- awaitTermination(long, TimeUnit):等待线程池关闭,超时返回 false。
👉 用 Callable 拿任务返回值
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
// 用Callable获取任务返回值
public class ExecutorCallableDemo {
// 有返回值的任务:计算商品折扣后价格
private static Callable<Double> calculateDiscountPrice(String productId, double price) {
return () -> {
// 模拟计算耗时
Thread.sleep(50);
// 假设折扣是9折
double discountPrice = price * 0.9;
System.out.println(Thread.currentThread().getName() + ":商品" + productId + "折扣价计算完成");
return discountPrice;
};
}
public static void main(String[] args) throws Exception {
ExecutorService executor = Executors.newSingleThreadExecutor();
// 提交Callable任务
Future<Double> future = executor.submit(calculateDiscountPrice("P1001", 6999.0));
// 干别的事,不用等结果
System.out.println("主线程:我先处理其他事情...");
// 拿结果(如果没完成会阻塞,直到完成)
double discountPrice = future.get();
System.out.println("商品P1001折扣价:" + discountPrice + "元");
executor.shutdown();
}
}
五、总结:Executor 核心心法(王二刻在心里)
- 别再 new Thread:线程是宝贵资源,复用才是王道,Executor 帮你管线程;
- Executor 是工头:你只提交任务,线程的创建、复用、销毁全交给它;
- ExecutorService 管生命周期:submit 提交任务,shutdown 关闭线程池,别忘收尾;
- 线程池是核心:FixedThreadPool、CachedThreadPool 按需选,避免线程泛滥。
➡️ 哇哥的面试彩蛋
“面试时被问‘为什么用 Executor 而不是 new Thread’,你就说三点,” 哇哥弹了弹烟灰,“一是线程复用,减少开销;二是解耦任务和线程,代码简洁;三是便于管理,能控制线程数量,防止 OOM—— 这三点一讲,面试官就知道你不是背概念的菜鸟。”

关注我,下一篇咱们扒一扒 ThreadPoolExecutor 的底细 —— 这个 “工头” 是怎么管理工人的?核心参数是什么意思?面试时被问 “核心线程数和最大线程数的区别”,怎么用流水线的例子怼回去?让你把 Executor 框架的根挖透!
171万+

被折叠的 条评论
为什么被折叠?



