Java 线程池:7参数配置、4拒绝策略与执行流程详解

embedded/2025/2/8 21:10:32/

1. 为什么需要线程池

在 Java 并发编程中,线程的创建和销毁是一项昂贵的操作。频繁地创建和销毁线程会带来较高的系统开销,甚至可能因线程数过多而导致 OOM(OutOfMemoryError)CPU 过载
线程池(Thread Pool) 的设计初衷正是为了解决这些问题。

线程池的主要优点:

  • 降低资源消耗:通过复用线程,避免频繁的创建和销毁。
  • 提高响应速度:任务到达时可复用现有线程,无需等待线程创建。
  • 增强可管理性:提供任务队列和线程管理能力,防止资源耗尽。

2. 线程池的核心组成

Java 提供了 ThreadPoolExecutor 作为线程池的核心实现,构造方法包含多个参数,主要负责线程池的各种行为控制:

2.1. 构造方法(7 个参数)

java">public ThreadPoolExecutor(int corePoolSize,         // 核心线程数int maximumPoolSize,      // 最大线程数long keepAliveTime,       // 空闲线程存活时间TimeUnit unit,            // 时间单位BlockingQueue<Runnable> workQueue, // 任务队列ThreadFactory threadFactory,       // 线程工厂RejectedExecutionHandler handler   // 拒绝策略
)

参数详解:

参数名说明
corePoolSize核心线程数,线程池始终保持的最小线程数量。
maximumPoolSize最大线程数,线程池可创建的最大线程数。
keepAliveTime非核心线程空闲超过该时间会被销毁。
unitkeepAliveTime 的时间单位。
workQueue用于存放等待执行的任务。
threadFactory创建新线程的工厂,可自定义线程属性。
handler线程池无法接收新任务时的拒绝策略。

示例:创建线程池

java">ThreadPoolExecutor executor = new ThreadPoolExecutor(2, 4, 60, TimeUnit.SECONDS, new LinkedBlockingQueue<>(10),Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy()
);
  • 核心线程数为 2,最大线程数为 4
  • 多余线程在空闲 60 秒后销毁
  • 任务队列最多容纳 10 个任务
  • 使用默认线程工厂创建线程
  • 拒绝策略为 AbortPolicy,即直接抛出异常

3. 创建线程池的方式

3.1. 使用 Executors 创建线程池

Java 提供了 Executors 工具类,可以快速创建不同类型的线程池

java">ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);
ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(3);
线程池类型创建方法特点
固定大小线程池newFixedThreadPool(n)线程数固定,适合长期任务。
缓存线程池newCachedThreadPool()线程数不固定,适合短期任务。
线程池newSingleThreadExecutor()只有一个线程,任务顺序执行。
调度线程池newScheduledThreadPool(n)支持定时和周期性任务。

3.2. 推荐使用 ThreadPoolExecutor 创建线程池

尽管 Executors 提供了便捷的方法,但其内部参数可能存在潜在风险,例如 无界队列可能导致 OOM。因此,推荐显式使用 ThreadPoolExecutor 并自定义参数:

java">ThreadPoolExecutor threadPool = new ThreadPoolExecutor(2, 4, 60, TimeUnit.SECONDS,new LinkedBlockingQueue<>(2),Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy()
);

4. 线程池的任务执行流程

线程池任务执行过程可以分为以下几个阶段:

任务提交
线程数 < corePoolSize ?
创建新线程执行任务
任务队列已满 ?
任务加入队列
线程数 < maximumPoolSize ?
创建新线程
执行拒绝策略
线程执行完成
线程是否空闲超时 ?
线程回收

4.1. 任务执行流程说明

  1. 线程数 < 核心线程数(corePoolSize):创建新线程执行任务。
  2. 线程数达到核心线程数,任务进入任务队列,等待执行。
  3. 任务队列已满,且线程数小于最大线程数(maximumPoolSize):创建新线程执行任务。
  4. 线程数达到 maximumPoolSize 且任务队列已满:执行拒绝策略。

4.2. 线程池中的两阶段回收机制

线程池中的线程回收遵循两阶段机制:

  1. 核心线程(corePoolSize 内的线程)
    核心线程默认情况下是长期存活的,除非显式调用 allowCoreThreadTimeOut(true),才会在空闲超过 keepAliveTime 后被回收。
java">executor.allowCoreThreadTimeOut(true);
  1. 非核心线程(超过 corePoolSize 的线程)
    非核心线程会在空闲超过 keepAliveTime 后自动销毁,防止资源浪费。

5. 线程池的拒绝策略

当任务无法被线程池接受时,ThreadPoolExecutor 提供了 4 种内置拒绝策略:

拒绝策略描述
AbortPolicy抛出 RejectedExecutionException(默认策略)。
CallerRunsPolicy由提交任务的线程执行该任务,减轻线程池压力。
DiscardPolicy丢弃该任务,不抛出异常。
DiscardOldestPolicy丢弃队列中最早的任务,尝试执行当前任务。

示例:

java">new ThreadPoolExecutor(2, 4, 60, TimeUnit.SECONDS,new LinkedBlockingQueue<>(2),Executors.defaultThreadFactory(),new ThreadPoolExecutor.CallerRunsPolicy());

6. 常见问题与优化建议

  1. 避免使用 Executors 创建线程池,推荐显式参数化 ThreadPoolExecutor
  2. 合理配置 corePoolSizemaximumPoolSize
    • CPU 密集型任务(如计算):corePoolSize = CPU 核数 + 1
    • I/O 密集型任务(如数据库操作):corePoolSize = CPU 核数 * 2
  3. 选择合适的任务队列
    • LinkedBlockingQueue:适用于任务较多时,防止任务丢失。
    • SynchronousQueue:适用于高吞吐、低延迟场景。
  4. 监控线程池状态
java">System.out.println("Active threads: " + threadPool.getActiveCount());
System.out.println("Task queue size: " + threadPool.getQueue().size());
  1. 避免线程池泄漏:执行完任务后,调用 shutdown()shutdownNow() 释放资源。

7. 总结

  • 线程池能有效提升并发性能,减少系统资源开销。
  • Executors 提供便捷的方法,但推荐使用 ThreadPoolExecutor 来显式配置线程池参数。
  • 合理配置线程池参数,并根据任务类型优化,是高效并发编程的关键。

http://www.ppmy.cn/embedded/160623.html

相关文章

组合(力扣77)

从这道题开始&#xff0c;我们正式进入回溯算法的学习。之前在二叉树中只是接触到了一丢丢&#xff0c;而这里我们将使用回溯算法解决很多经典问题。 那么这道题是如何使用回溯算法的呢&#xff1f;在讲回溯之前&#xff0c;先说明一下此题是如何递归的。毕竟回溯递归不分家&a…

怎么编写AI模型prompt(提问,表达需求)

在编写用于与AI模型交互的prompt时&#xff0c;遵循一些最佳实践可以提高交互的效果和效率。以下是一些编写prompt的规范和建议&#xff1a; 1. 明确目标 清晰表达需求&#xff1a;确保你的prompt明确表达了你希望AI完成的任务或回答的问题。避免模糊或含糊不清的表述。具体化…

大模型 Llama 微调如何适配中文_词表扩展

Llama 是 Meta AI 开源的一系列大型语言模型 (LLM)&#xff0c;在各种 NLP 任务上表现出色。然而&#xff0c;Llama 主要是在英文语料上进行预训练的&#xff0c;对中文的支持相对较弱。为了让 Llama 更好地服务于中文用户&#xff0c;我们需要对其进行微调 (Fine-tuning)&…

NodeList 对象

NodeList 对象 概述 NodeList 对象是 DOM(文档对象模型)中的一种特殊类型,它代表了文档中一组元素的集合。NodeList 对象通常通过查询 DOM 树来获取,例如使用 document.querySelectorAll() 方法。NodeList 对象在 JavaScript 中非常有用,因为它允许开发者以编程方式遍历…

【Day34 LeetCode】动态规划DP Ⅶ 打家劫舍

一、动态规划DP Ⅶ 打家劫舍 1、打家劫舍198 首先确定dp数组&#xff0c;dp[i]表示从0~i房间最大可以获得的金额数 然后确定dp方程&#xff0c;对于当前房间i&#xff0c;dp[i]取决于偷不偷当前房间&#xff0c;如果偷当前房间&#xff0c;则前一个房间不能包括&#xff0c;如…

【Spring Boot】自动配置源码解析

目录 Spring-Boot-Starter 一、准备配置类和 Bean 对象二、自动配置条件依赖三、Bean 的参数获取 3.1 EnableConfigurationProperties 注解3.2 ConfigurationProperties 注解 四. Bean 的发现 4.1 自己项目的 Bean 扫描4.2 jar 包的 Bean 扫描 五. Bean 的加载 自动配置总结 …

RabbitMQ深度探索:死信队列

死信队列产生背景&#xff1a; RabbitMQ 死信队列俗称 备胎队列&#xff1a;消息中间件因为某种原因拒收该消息后&#xff0c;可以转移到私信队列中存放&#xff0c;死信队列也可以有交换机和路由 key 等 生产死信队列的原因&#xff1a; 消息投递到 MQ 存放&#xff0c;消息已…

通信易懂唠唠SOME/IP——SOME/IP消息格式

SOME/IP是Scalable service-Oriented MiddlewarE over IP (SOME/IP)的缩写&#xff0c;基于IP的可扩展面向服务的中间件。广泛应用于汽车行业嵌入式通信。 它是基于服务的&#xff0c;服务可以由0个或多个Event,Method,Field组成。 Event是一种单向的数据传输&#xff0c;在数…