每次需要异步执行任务就new Thread()?这在生产环境是灾难。
线程的创建和销毁是有开销的,无限制创建线程会导致资源耗尽。Java线程池就是来解决这个问题的------复用线程、控制并发数、管理任务队列。
为什么不用new Thread
java
// 每次请求都创建新线程 ------ 大忌
public void handleRequest(Request req) {
new Thread(() -> {
process(req);
}).start();
}
// 1000个并发请求 = 1000个线程 = 内存爆炸
| 问题 | 说明 |
|---|---|
| 资源浪费 | 每个线程占用约1MB栈内存 |
| 频繁创建销毁 | 线程创建需要操作系统介入,开销大 |
| 无管理 | 没有队列、没有拒绝策略、没有监控 |
核心参数
java
ThreadPoolExecutor executor = new ThreadPoolExecutor(
4, // corePoolSize:核心线程数
8, // maximumPoolSize:最大线程数
60, TimeUnit.SECONDS, // keepAliveTime:非核心线程空闲存活时间
new LinkedBlockingQueue<>(100), // 任务队列
new ThreadPoolExecutor.CallerRunsPolicy() // 拒绝策略
);
任务提交流程:
markdown
新任务提交
↓
核心线程数满了?
├─ 否 → 创建核心线程执行
└─ 是 ↓
队列满了?
├─ 否 → 放入队列等待
└─ 是 ↓
最大线程数满了?
├─ 否 → 创建非核心线程执行
└─ 是 → 执行拒绝策略
四种拒绝策略
| 策略 | 行为 |
|---|---|
| AbortPolicy(默认) | 抛出RejectedExecutionException |
| CallerRunsPolicy | 让提交任务的线程自己执行 |
| DiscardPolicy | 直接丢弃,不报错 |
| DiscardOldestPolicy | 丢弃队列中最老的任务 |
四种内置线程池
java
// 固定大小线程池
ExecutorService fixed = Executors.newFixedThreadPool(4);
// 缓存线程池(来多少创建多少,空闲60s回收)
ExecutorService cached = Executors.newCachedThreadPool();
// 单线程池(保证任务顺序执行)
ExecutorService single = Executors.newSingleThreadExecutor();
// 定时线程池
ScheduledExecutorService scheduled = Executors.newScheduledThreadPool(2);
scheduled.scheduleAtFixedRate(() -> {
System.out.println("每10秒执行一次");
}, 0, 10, TimeUnit.SECONDS);
注意:阿里规范禁止使用Executors创建线程池,因为:
newFixedThreadPool和newSingleThreadExecutor用的是无界队列(LinkedBlockingQueue无参构造),任务堆积可能导致OOMnewCachedThreadPool最大线程数是Integer.MAX_VALUE,可能创建大量线程导致OOM
推荐直接用ThreadPoolExecutor手动创建,参数自己控制。
实际项目中的配置
java
@Configuration
public class ThreadPoolConfig {
@Bean("businessPool")
public ThreadPoolExecutor businessPool() {
return new ThreadPoolExecutor(
Runtime.getRuntime().availableProcessors() * 2, // CPU核心数×2
Runtime.getRuntime().availableProcessors() * 4,
60, TimeUnit.SECONDS,
new LinkedBlockingQueue<>(1000),
new ThreadFactoryBuilder().setNameFormat("biz-pool-%d").build(),
new ThreadPoolExecutor.CallerRunsPolicy()
);
}
}
// 使用
@Service
public class OrderService {
@Autowired
@Qualifier("businessPool")
private ThreadPoolExecutor executor;
public void createOrder(Order order) {
CompletableFuture.supplyAsync(() -> {
// 异步处理
return processOrder(order);
}, executor).thenAccept(result -> {
log.info("订单处理完成: {}", result);
});
}
}
线程池大小怎么定
| 任务类型 | 推荐线程数 | 理由 |
|---|---|---|
| CPU密集型 | CPU核心数 + 1 | 减少线程切换开销 |
| IO密集型 | CPU核心数 × 2 ~ × N | 线程大部分时间在等IO |
| 混合型 | 根据任务比例调整 | IO多就多开线程 |
java
// 获取CPU核心数
int cpuCores = Runtime.getRuntime().availableProcessors();
// IO密集型经验公式
int ioThreads = (int)(cpuCores / (1 - 阻塞系数));
// 阻塞系数 = 线程等待时间 / (线程等待时间 + 线程计算时间)
优雅关闭
java
// 优雅关闭线程池
public void shutdown() {
executor.shutdown(); // 不再接受新任务,但会处理完队列中的任务
try {
if (!executor.awaitTermination(30, TimeUnit.SECONDS)) {
executor.shutdownNow(); // 超时强制关闭
}
} catch (InterruptedException e) {
executor.shutdownNow();
}
}
线程池是Java并发编程的标配。记住:永远用ThreadPoolExecutor手动创建,别用Executors的快捷方法。参数根据业务场景调,别用默认值。
你们项目的线程池参数是怎么设的?