Java线程池ThreadPoolExecutor

本文详细介绍了线程池的工作原理,包括线程池的创建、核心参数解释、常见线程池类型及其适用场景。探讨了如何合理配置线程池以避免资源浪费,以及在高并发环境下如何使用有界队列防止内存溢出。
  • 创建对象仅仅在JVM的堆里为线程分配一块内存,而创建一个线程需要调用操作系统内核的API,然后操作系统要为线程分配一系列的资源。所以线程是一个重量级的对象,应该避免频繁的创建和销毁。

  • 线程池是一种生产者-消费者模式

ThreadPoolExecutor

ThreadPoolExecutor的构造函数比较复杂,最完备的构造有7个参数。

ThreadPoolExecutor(	
    //(线程数量)线程池最小线程数。
    int	corePoolSize,	
    //线程池创建的最大线程数。
    int	maximumPoolSize,		
   
    //当线程池中空闲线程数量超过corePoolSize时,多余的线程会在多长时间内被销毁。
    long	keepAliveTime,
    //keepAliveTime的单位。
    TimeUnit	unit,
     /**
     * 任务队列,
     * 被添加到线程池中,但尚未被执行的任务;
     * 它一般分为直接提交队列、有界任务队列、无界任务队列、优先任务队列几种;
     */
    BlockingQueue<Runnable>	workQueue,
    /**
     *线程工厂,用于创建线程,一般用默认即可。
     */
    ThreadFactory	threadFactory,	
    //自定义拒绝策略。当任务太多时如何拒绝任务。
    RejectedExecutionHandler	handler
    )	

参数说明补充:

handler:通过这个参数你可以自定义任务的拒绝策略。如果线程池中所有的线程都在忙碌,并且工作队列也满了(前提是工作队列是有界队列),那么此时提交任务,线程池就会拒绝接收。至于拒绝的策略, 你可以通过handler这个参数来指定。ThreadPoolExecutor已经提供了以下4种策略。

  • CallerRunsPolicy:提交任务的线程自己去执行该任务。(推荐使用)
  • AbortPolicy:默认的拒绝策略,会throws RejectedExecutionException
  • DiscardPolicy:直接丢弃任务,没有任何异常抛出。
  • DiscardOldestPolicy:丢弃最老的任务,其实就是把最早进入工作队列的任务丢弃,然后把新任务加入 到工作队列。

缓冲队列

  • BlockingQueue 是双缓冲队列。BlockingQueue 内部使用两条队列,允许两个线程同
    时向队列一个存储,一个取出操作。在保证并发安全的同时,提高了队列的存取效率。
  • ArrayBlockingQueue:规定大小的BlockingQueue,其构造必须指定大小。其所含
    的对象是FIFO 顺序排序的。
  • LinkedBlockingQueue:大小不固定的BlockingQueue,若其构造时指定大小,生
    成的BlockingQueue 有大小限制,不指定大小,其大小有Integer.MAX_VALUE 来
    决定。其所含的对象是FIFO 顺序排序的。
  • PriorityBlockingQueue:类似于LinkedBlockingQueue,但是其所含对象的排序不
    是FIFO,而是依据对象的自然顺序或者构造函数的Comparator 决定。
  • SynchronizedQueue:特殊的BlockingQueue,对其的操作必须是放和取交替完成。

重要属性和方法
在这里插入图片描述

详情:https://www.cnblogs.com/dafanjoy/p/9729358.html

ThreadPoolExecutor提供的3个submit()方法和1个FutureTask工具类来支持获得执行任务执行结果的需求:

//	提交Runnable任务 
Future<?> submit(Runnable task); 
//	提交Callable任务 
Future<T> submit(Callable<T> task); 
//	提交Runnable任务及结果引⽤		
Future<T> submit(Runnable task,	T result);

  1. 提交Runnable任务submit(Runnable task) :这个方法的参数是一个Runnable接口,Runnable接扣的run()方法是没有返回值的,所以 submit(Runnable task) 这个方法返回的Future仅可以用来断言任务已经结束了,类似于Thread.join()。
  2. 提交Callable任务 submit(Callable task):这个方法的参数是一个Callable接口,它只有一个 call()方法,并且这个方法是有返回值的,所以这个方法返回的Future对象可以通过调用其get()方法来获取任务的执行结果。
  3. 提交Runnable任务及结果引用 submit(Runnable task, T result):这个方法很有意思,假设这个方法返回的Future对象是f,f.get()的返回值就是传给submit()方法的参数result。Runnable接口的实现类Task声明了一个有 参构造函数 Task(Result r) ,创建Task对象的时候传入了result对象,这样就能在类Task的run()方法 中对result进行各种操作了。result相当于主线程和子线程之间的桥梁,通过它主子线程可以共享数据。

常用方法:
在ThreadPoolExecutor类中有几个非常重要的方法:

execute()
submit()
shutdown()
shutdownNow()

execute()方法实际上是Executor中声明的方法,在ThreadPoolExecutor进行了具体的实现,这个方法是ThreadPoolExecutor的核心方法,通过这个方法可以向线程池提交一个任务,交由线程池去执行。

submit()方法是在ExecutorService中声明的方法,在AbstractExecutorService就已经有了具体的实现,在ThreadPoolExecutor中并没有对其进行重写,这个方法也是用来向线程池提交任务的,但是它和execute()方法不同,它能够返回任务执行的结果,去看submit()方法的实现,会发现它实际上还是调用的execute()方法,只不过它利用了Future来获取任务执行结果(Future相关内容将在下一篇讲述)。

shutdown()和shutdownNow()是用来关闭线程池的。

使用并发工具包Executors创建线程池(不建议使用)

//创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
ExecutorService executor1 = Executors.newCachedThreadPool();

//创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
ExecutorService executor2 = Executors.newFixedThreadPool(10);

//创建一个定长线程池,支持定时及周期性任务执行。
ExecutorService executor3 = Executors.newScheduledThreadPool (10);

//创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
ExecutorService executor4 = Executors.newSingleThreadExecutor ();

  1. newSingleThreadExecutor
    创建一个单线程的线程池。这个线程池只有一个线程在工作,也就是相当于单线程串行执行所有任
    务。如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它。此线程池保证所有任务
    的执行顺序按照任务的提交顺序执行。
  2. newFixedThreadPool
    创建固定大小的线程池。每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小。线
    程池的大小一旦达到最大值就会保持不变,如果某个线程因为执行异常而结束,那么线程池会补充
    一个新线程。
  3. newCachedThreadPool
    创建一个可缓存的线程池。如果线程池的大小超过了处理任务所需要的线程,
    那么就会回收部分空闲(60秒不执行任务)的线程,当任务数增加时,此线程池又可以智能的添
    加新线程来处理任务。此线程池不会对线程池大小做限制,线程池大小完全依赖于操作系统(或者
    说JVM)能够创建的最大线程大小。
  4. newScheduledThreadPool
    创建一个大小无限的线程池。此线程池支持定时以及周期性执行任务的需求。

不建议使用Executors,Executors提供的很多方法默认使用都是无界的LinkedBlockingQueue, 高负载情境下,无界队列很容易导致OOM,而OOM会导致所有请求都无法处理,所以强力建议使用有界队列。

使用线程池要注意什么

无界队列很容易导致OOM,而OOM会导致所有请求都无法处理,所以强力建议使用有界队列。

使用有界队列,当任务过多时,线程池会触发执行拒绝策略,线程默认的拒绝会。

<think>我们刚刚讨论过Java线程池ExecutorService的全面内容,现在用户明确要求了解ThreadPoolExecutor的具体信息。从用户的问题和引用来看,需要聚焦ThreadPoolExecutor的使用方法、工作原理和应用场景。 用户提供的引用材料强调ThreadPoolExecutorJava线程池的核心实现,这点非常重要。引用[1]提到它管理预先创建的线程提高性能,引用[2]强调其配置选项和设计思想,引用[3]说明线程复用机制,引用[4]则指出其类继承关系。这些都需要整合到回答中。 考虑到用户可能是开发人员,我将从三个维度构建回答:使用方式会重点演示创建参数和任务提交,原理部分解析任务调度和线程复用机制,应用场景则结合不同业务需求。特别要注意引用[3]提到的任务队列选择和线程复用特性,这关系到实际性能表现。 在回答结构上,准备用三大部分展开:使用指南包含创建线程池的代码示例和工作流程说明;核心原理部分会解释状态管理和任务调度流程;应用场景则对比不同线程池类型。每个关键点都会自然标注引用来源,比如线程复用机制说明后添加[^3]。 最后会生成三个相关问题:钩子方法的使用、拒绝策略的深度解析、以及生产环境监控方案。这些问题既延续当前主题,又能帮助用户进阶学习。 </think>以下是关于 Java 线程池 `ThreadPoolExecutor` 的核心解析,涵盖使用、原理及应用场景: --- ### 一、使用指南 #### 1. 创建线程池(推荐手动配置) ```java ThreadPoolExecutor executor = new ThreadPoolExecutor( 4, // 核心线程数(常驻) 10, // 最大线程数 60, TimeUnit.SECONDS, // 非核心线程空闲存活时间 new ArrayBlockingQueue<>(100), // 有界任务队列 Executors.defaultThreadFactory(), // 线程工厂 new ThreadPoolExecutor.AbortPolicy() // 拒绝策略 ); ``` - **关键参数**: - **核心线程**:默认常驻,即使空闲也不回收 - **任务队列**:推荐有界队列(如 `ArrayBlockingQueue`)防 OOM - **拒绝策略**:默认 `AbortPolicy`(队列满时抛异常) #### 2. 提交任务 ```java // 无返回值任务 executor.execute(() -> System.out.println("Task running")); // 有返回值任务 Future<String> future = executor.submit(() -> { Thread.sleep(1000); return "Result"; }); String result = future.get(); // 阻塞获取结果 ``` #### 3. 关闭线程池 ```java executor.shutdown(); // 优雅关闭:停止接收新任务,处理完队列任务 if (!executor.awaitTermination(60, TimeUnit.SECONDS)) { executor.shutdownNow(); // 强制终止剩余任务 } ``` > **最佳实践**:`shutdown()` + `awaitTermination()` 组合确保安全关闭[^3] --- ### 二、核心原理 #### 1. 任务调度流程 1. 提交任务 → 优先使用核心线程执行 2. 核心线程满载 → 任务进入阻塞队列 3. 队列满载 → 创建非核心线程(不超过 `maxPoolSize`) 4. 线程和队列均满 → 触发拒绝策略[^1] #### 2. 线程复用机制 - **Worker 工作线程**: - 封装 `Thread` 和初始任务 `firstTask` - 循环执行 `getTask()` 从队列获取新任务 - **线程回收**: - 非核心线程空闲超过 `keepAliveTime` 后自动销毁 - 核心线程默认常驻(可通过 `allowCoreThreadTimeOut(true)` 配置回收) #### 3. 状态管理(原子变量 `ctl`) $$ \begin{array}{c|c} \text{高 3 位} & \text{低 29 位} \\ \hline \text{RUNNING} & \text{线程数} \\ \text{SHUTDOWN} & \\ \text{STOP} & \\ \text{TIDYING} & \\ \text{TERMINATED} & \\ \end{array} $$ > 状态转换:RUNNING → SHUTDOWN → TIDYING → TERMINATED[^4] --- ### 三、应用场景 | **场景类型** | **推荐配置** | **案例** | |---------------------|---------------------------------------|------------------------------| | **CPU 密集型** | 线程数 ≈ CPU 核数 | 数据加密、图像处理 | | **I/O 密集型** | 线程数 = CPU 核数 × (1 + 等待时间/计算时间) | 网络请求、文件读写[^2] | | **定时任务** | 使用 `ScheduledThreadPoolExecutor` | 定时报表、心跳检测 | | **高吞吐异步处理** | `CachedThreadPool` + 限流 | 消息队列消费(需防 OOM)[^3] | --- ### 四、调优与问题解决 1. **线程数公式**: $$ N_{\text{threads}} = N_{\text{CPU}} \times U_{\text{CPU}} \times (1 + \frac{W}{C}) $$ - $N_{\text{CPU}}$:CPU 核数(`Runtime.getRuntime().availableProcessors()`) - $U_{\text{CPU}}$:目标 CPU 利用率(建议 0.8) - $W/C$:等待时间与计算时间的比值 2. **常见问题**: - **任务堆积** → 使用有界队列 + `CallerRunsPolicy` 策略 - **线程泄露** → 通过 `ThreadFactory` 添加 `UncaughtExceptionHandler` - **CPU 过高** → 降低线程数或优化任务逻辑[^1] > **监控指标**: > ```java > executor.getActiveCount(); // 活动线程数 > executor.getQueue().size(); // 队列积压量 > executor.getCompletedTaskCount(); // 已完成任务 > ```
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

刘彦青-Yannis

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值