Skip to content

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 项目通常用配置好的 ThreadPoolTaskExecutor Bean 或 @Async 注解,不用每次手写池子——学 Spring 时会见到。理解上面"核心线程 → 队列 → 最大线程 → 拒绝策略"这条链,能看懂配置含义就行。

4. 并发集合:线程安全的容器

多线程环境里,HashMapArrayList 这些普通集合都不是线程安全的——多线程同时读写会出问题(跟 count++ 类似的竞态)。java.util.concurrent 包提供了线程安全版本,用法和普通集合几乎一样:

普通集合并发版本说明
HashMapConcurrentHashMap并发首选,性能好
ArrayListCopyOnWriteArrayList读多写少:写时复制整个数组(读不用锁)
HashSetCopyOnWriteArraySet同上
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. 小结

  1. 线程池 = 复用线程、限制数量,比手动 new Thread 可靠
  2. 简单场景用 Executors.newFixedThreadPool(n);高并发场景手写 ThreadPoolExecutor 限制队列
  3. Spring 项目用 ThreadPoolTaskExecutor / @Async,不用自己手写
  4. 多线程环境用 ConcurrentHashMap 等并发集合,别用普通集合
  5. 虚拟线程(了解):便宜的线程,IO 密集场景的利器,JDK 21+ 原生支持