Appearance
2026-08-29 · 不手动 new 线程,交给线程池管理——并发任务的标准做法。
Java 线程池与虚拟线程
1. 为什么需要线程池
手动 new Thread() 虽然简单,但真实项目里不会这么干,有三个问题:
| 问题 | 说明 |
|---|---|
| 频繁创建/销毁线程 | 创建线程是要向操作系统申请资源的,开销很大 |
| 线程数量失控 | 1000 个请求就 1000 个线程,内存压力大 |
| 缺少复用 | 用完即销毁,没有复用 |
线程池(Thread Pool) = 提前创建好一批线程放池子里,有任务就派一个线程去干,干完线程不销毁,回池子待命,下一个任务继续用。
任务来了 → 从池子里拿空闲线程 → 执行 → 线程回池子(不销毁)
(池子里没空闲线程 → 任务排队等待)2. 最简单的线程池:Executors 工具类
java
import java.util.concurrent.*;
// 固定 3 个线程的池子
ExecutorService pool = Executors.newFixedThreadPool(3);
// 提交任务(Runnable:不需要返回值)
pool.execute(() -> System.out.println("任务执行中:" + Thread.currentThread().getName()));
// 提交任务(Callable:需要返回值)
Future<Integer> future = pool.submit(() -> 1 + 2);
System.out.println(future.get()); // 3
// 用完关闭(不关的话程序不会退出)
pool.shutdown();三个常用工厂方法:
| 方法 | 线程数 | 适用场景 |
|---|---|---|
newFixedThreadPool(n) | 固定 n 个 | 任务量稳定,最常用 |
newCachedThreadPool() | 按需创建,空闲 60 秒回收 | 任务很多但都很短 |
newSingleThreadExecutor() | 只有 1 个 | 任务必须串行执行(排队) |
提交 Runnable 用
execute(),提交 Callable 用submit()(它返回Future拿结果)。
3. 线程池的关键参数(了解)
ThreadPoolExecutor 有 7 个核心参数,决定了池子怎么运转:
java
ThreadPoolExecutor pool = new ThreadPoolExecutor(
2, // 核心线程数(常驻,不回收)
5, // 最大线程数
60, TimeUnit.SECONDS, // 空闲存活时间:超过核心数的线程空闲 60s 就回收
new ArrayBlockingQueue<>(10), // 任务队列:核心线程都忙时,任务先排队
Executors.defaultThreadFactory(), // 线程工厂
new ThreadPoolExecutor.AbortPolicy() // 拒绝策略:队列也满了怎么办
);任务处理流程:任务来了先找空闲核心线程 → 没有就排队 → 队列满了且线程数没到上限就开新线程 → 线程也满了触发拒绝策略。
两个实际问题:
- Executors 的坑:
newFixedThreadPool的队列是无界队列(可以无限排队)——任务一多队列无限增长,内存耗尽。所以真正的高并发场景要手写ThreadPoolExecutor指定队列大小。 - 拒绝策略:默认
AbortPolicy(直接抛异常),还有其他策略(让提交方自己执行、丢最老任务等),业务上按需选择。
实际开发里,Spring 项目通常用配置好的
ThreadPoolTaskExecutorBean 或@Async注解,不用每次手写池子——学 Spring 时会见到。理解上面"核心线程 → 队列 → 最大线程 → 拒绝策略"这条链,能看懂配置含义就行。
4. 并发集合:线程安全的容器
多线程环境里,HashMap、ArrayList 这些普通集合都不是线程安全的——多线程同时读写会出问题(跟 count++ 类似的竞态)。java.util.concurrent 包提供了线程安全版本,用法和普通集合几乎一样:
| 普通集合 | 并发版本 | 说明 |
|---|---|---|
HashMap | ConcurrentHashMap | 并发首选,性能好 |
ArrayList | CopyOnWriteArrayList | 读多写少:写时复制整个数组(读不用锁) |
HashSet | CopyOnWriteArraySet | 同上 |
java
// 直接用并发版本,替换普通集合即可
Map<String, String> cache = new ConcurrentHashMap<>();
cache.put("key", "value");
// 读多写少的场景(如配置列表、菜单列表)
List<String> list = new CopyOnWriteArrayList<>();多线程场景直接用
ConcurrentHashMap,不要用Collections.synchronizedMap(锁粒度粗、性能差)。
5. 虚拟线程(Virtual Threads)——了解即可
它解决什么问题
传统线程(平台线程)由操作系统管理,每个线程要占 1MB 左右的内存。一次开 10 万个线程,内存直接爆。
虚拟线程 = JVM 自己管理的"轻量级线程"(Java 21 正式版,JDK 25 原生支持)。它不占操作系统线程,内存开销极小(几 KB 一个),一次开几十万个没问题。
传统线程:1 个 = 操作系统 1 个线程 = 约 1MB 内存 → 上限几千~几万
虚拟线程:1 个 = JVM 里的一条"轻量任务" = 几 KB → 上限十万级怎么用(和普通线程几乎一样)
java
// 每任务一个虚拟线程(默认会池化复用)
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
executor.submit(() -> System.out.println("虚拟线程执行"));
}什么时候用
- 适用:大量并发、但每个任务大部分时间在等待(IO 阻塞)的场景——比如同时请求 1 万个外部接口、读 1 万个文件。虚拟线程让"等待"几乎零成本。
- 不适用:CPU 密集计算(一直算不停)、线程数本来只有几十个——用不用没区别。
Java 21 后 Spring Boot 3.2+ 支持一键开启虚拟线程(配置项)。知道它是"便宜的线程"、IO 密集场景收益大即可,日常开发仍以传统线程池为主。
6. 小结
- 线程池 = 复用线程、限制数量,比手动 new Thread 可靠
- 简单场景用
Executors.newFixedThreadPool(n);高并发场景手写ThreadPoolExecutor限制队列 - Spring 项目用
ThreadPoolTaskExecutor/@Async,不用自己手写 - 多线程环境用
ConcurrentHashMap等并发集合,别用普通集合 - 虚拟线程(了解):便宜的线程,IO 密集场景的利器,JDK 21+ 原生支持