线程池

为什么需要线程池

Java 添加线程池工具是为了:

问题 线程池的解决方案 带来的好处
频繁创建/销毁线程开销大 线程复用 提升性能,降低资源消耗
线程创建导致延迟 预创建线程 提高响应速度
无限制创建线程风险高 统一管理,控制数量 提高系统稳定性和可管理性
功能单一 提供定时、拒绝策略等 增强功能性和健壮性

因此,线程池是构建高性能、高稳定性、可管理多线程应用程序的核心基础和必备工具。在 Java 中,java.util.concurrent 包下的线程池框架是并发编程的基石,几乎在所有需要处理并发任务的服务器端应用中都会用到。

线程池的创建方法

Java 中线程池的创建方式主要有两大类:

  1. 通过 Executors 工厂类(快速创建):提供了几种预设的线程池配置,方便快捷,适用于常见场景。
  2. 通过 ThreadPoolExecutor 构造函数(手动创建):提供了所有可配置参数,允许开发者进行更精细化的控制,这是《阿里巴巴Java开发手册》等规范推荐的方式,以避免使用Executors可能带来的风险。

方式一:使用Executors工厂类

Executors 是一个工具类,它提供了多个静态工厂方法来创建不同配置的线程池。

newFixedThreadPool - 固定大小线程池

1
2
3
4
5
6
7
8
9
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(5); // 核心线程数 = 最大线程数 = 5

// 提交任务
fixedThreadPool.execute(() -> {
System.out.println("任务被执行,线程:" + Thread.currentThread().getName());
});

// 关闭线程池
fixedThreadPool.shutdown();
  • 特点:创建一个固定大小的线程池。池中的线程数始终不变。
  • 工作机制
    • 如果所有线程都在活动状态,新任务会在无界队列(LinkedBlockingQueue)中等待,直到有线程可用。
  • 适用场景:适用于为了满足资源管理的需求,需要限制当前线程数量的场景,适用于负载较重的服务器。

newCachedThreadPool - 可缓存线程池

1
ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
  • 特点:创建一个可缓存的线程池。
  • 工作机制
    • 如果池中有空闲线程,则重用它们。
    • 如果没有空闲线程,则创建新线程添加到池中。
    • 默认情况下,闲置 60 秒的线程会被从池中移除。因此,长时间空闲的池不会消耗任何资源。
    • 使用的任务队列是同步移交队列SynchronousQueue),它不存储元素,每个插入操作必须等待另一个线程的对应移除操作。
  • 适用场景:适用于执行很多短期异步任务的小程序,或者是负载较轻的服务器。注意:任务数量激增时,可能会创建大量线程,导致资源耗尽。

newSingleThreadExecutor - 单线程线程池

1
ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
  • 特点:创建一个单线程化的线程池。
  • 工作机制:它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。同样使用无界队列。
  • 适用场景:适用于需要保证任务顺序执行,并且在任意时间点,不会有多个线程是活动的场景。

newScheduledThreadPool - 定时任务线程池

1
2
3
4
5
6
7
8
9
10
11
12
ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(3);

// 延迟执行
scheduledThreadPool.schedule(() -> System.out.println("5秒后执行"), 5, TimeUnit.SECONDS);

// 定期固定速率执行
scheduledThreadPool.scheduleAtFixedRate(() -> System.out.println("延迟1秒后,每3秒执行一次"), 1, 3, TimeUnit.SECONDS);

// 定期固定延迟执行
scheduledThreadPool.scheduleWithFixedDelay(() -> {
System.out.println("任务结束,等待2秒后再次执行");
}, 1, 2, TimeUnit.SECONDS);
  • 特点:创建一个固定大小的线程池,支持定时及周期性任务执行。
  • 适用场景:需要执行延迟或周期性任务,如心跳检测、数据同步等。

方式二:手动配置ThreadPoolExecutor

直接使用 ThreadPoolExecutor 的构造函数可以让你完全掌控线程池的各个核心参数,这是最推荐的方式,因为它能让你明确线程池的运行规则,避免资源耗尽的风险。

核心构造函数

1
2
3
4
5
6
7
8
9
public ThreadPoolExecutor(
int corePoolSize, // 核心线程数
int maximumPoolSize, // 最大线程数
long keepAliveTime, // 非核心空闲线程存活时间
TimeUnit unit, // 时间单位
BlockingQueue<Runnable> workQueue, // 任务队列
ThreadFactory threadFactory, // 线程工厂
RejectedExecutionHandler handler // 拒绝策略
)
1
2
3
4
5
6
7
8
ThreadPoolExecutor executor = new ThreadPoolExecutor(
10,
10,
5,
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(10),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy());

7大核心参数详解

  1. corePoolSize(核心线程数):线程池中始终保持存活的线程数量,即使它们是空闲的。
  2. maximumPoolSize(最大线程数):线程池允许创建的最大线程数量。
  3. keepAliveTime(非核心空闲线程存活时间):当线程数大于核心线程数时,多余的空闲线程在终止前等待新任务的最长时间。
  4. unit(时间单位)keepAliveTime 的时间单位(如 TimeUnit.SECONDS)。
  5. workQueue(任务队列/阻塞队列):用于保存等待执行的任务的阻塞队列。选择不同的队列决定了线程池的排队策略
    • ArrayBlockingQueue:有界队列,需要指定大小。
    • LinkedBlockingQueue:无界队列(默认容量为 Integer.MAX_VALUE),FixedThreadPoolSingleThreadExecutor 使用它。
    • SynchronousQueue:不存储元素的队列,每个插入操作必须等待一个移除操作,否则插入操作会一直阻塞。CachedThreadPool 使用它。
    • PriorityBlockingQueue:具有优先级的无界阻塞队列。
  6. threadFactory(线程工厂):用于创建新线程的工厂。可以用来设置线程名、线程组、优先级等,便于排查问题。
  7. handler(拒绝策略):当线程池和队列都已满时,如何处理新提交的任务。JDK 提供了4种策略:
    • ThreadPoolExecutor.AbortPolicy默认):抛出 RejectedExecutionException 异常。
    • ThreadPoolExecutor.CallerRunsPolicy:由提交任务的调用者线程(通常是 main 线程)自己来执行这个任务。
    • ThreadPoolExecutor.DiscardPolicy:直接丢弃新任务,不做任何处理。
    • ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列中最老的一个任务,然后尝试再次提交当前任务。

线程池工作原理

image-20250902183914355

线程池关闭

1
2
3
4
5
6
7
8
// 正在执行的任务 执行完成后关闭线程池
executorService.shutdown();

// 正在执行的线程 执行完成后关闭线程池(等待执行的线程不在执行)
executorService.shutdownNow();

// 等待线程池关闭 true/false
executorService.awaitTermination(5, TimeUnit.SECONDS)

submit和execute方法的区别

特性 execute() submit()
方法来源 Executor 接口 ExecutorService 接口
参数类型 仅接受 Runnable 接受 RunnableCallable
返回值 无返回值 (void) 返回 Future<?> 对象
异常处理 任务中的异常会直接抛出(在线程中) 异常被封装在 Future 中,调用 get() 时抛出
用途 简单的异步执行,不关心结果 需要获取执行结果或处理异常的任务