🧑🎓 个人主页:沉默的羔羊
📖 本章内容:【异步任务-线程池配置】
🌳Gitee仓库地址:👉🏽线程池配置
前言
- 👉🏽使用线程池可以带来以下好处
- 降低资源消耗。降低频繁创建、销毁线程带来的额外开销,复用已创建线程
- 降低使用复杂度。将任务的提交和执行进行解耦
- 我们只需要创建一个线程池,然后往里面提交任务就行
- 具体执行流程由线程池自己管理,降低使用复杂度
- 提高线程可管理性。能安全有效的管理线程资源,避免不加限制无限申请造成资源耗尽风险
- 提高响应速度。任务到达后,直接复用已创建好的线程执行
- 👉🏽ThreadPoolExecutor 都有哪些核心参数
核心线程数(corePoolSize)
最大线程数(maximumPoolSize)
空闲线程超时时间(keepAliveTime)
时间单位(unit)
阻塞队列(workQueue)
拒绝策略(handler)
线程工厂(ThreadFactory)
- 👉🏽任务拒绝策略
AbortPolicy:丢弃任务,抛运行时异常
CallerRunsPolicy:由调用者线程去执行
DiscardPolicy:直接丢弃任务本身
DiscardOldestPolicy:从队列中踢出最先进入队列(最后一个执行)的任务
- 👉🏽线程池的执行流程
- 判断线程池的状态,如果不是RUNNING状态,直接执行拒绝策略
- 如果 当前线程数 < 核心线程池,则新建一个线程来处理提交的任务
- 如果 当前线程数 > 核心线程数且任务队列没满,则将任务放入阻塞队列等待执行
- 如果 核心线程池 < 当前线程池数 < 最大线程数且任务队列已满,则创建新的线程执行提交的任务
- 如果 当前线程数 > 最大线程数且队列已满,则执行拒绝策略拒绝该任务
一、基础配置
🧭 引入依赖
<!-- pool 对象池 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
🧭 配置信息
# 线程池配置
thread:
corePoolSize: 50 # 核心线程池大小
maxPoolSize: 200 # 最大可创建的线程数
queueCapacity: 1000 # 队列最大长度
keepAliveSeconds: 300 # 线程池维护线程所允许的空闲时间
/**
* @Author Michale
* @CreateDate 2022/9/16
* @Describe 读取线程池配置信息
*/
@Data
@Component
@ConfigurationProperties(prefix = "thread")
public class ThreadProperties {
@ApiModelProperty("核心线程池大小 ")
private int corePoolSize = 50;
@ApiModelProperty("最大可创建的线程数")
private int maxPoolSize = 200;
@ApiModelProperty(" 队列最大长度")
private int queueCapacity = 1000;
@ApiModelProperty("线程池维护线程所允许的空闲时间")
private int keepAliveSeconds = 300;
}
二、配置线程池
/**
* @Author Michale
* @CreateDate 2022/9/16
* @Describe 配置线程池
*/
@Configuration
public class ThreadPoolConfig {
@Autowired
private ThreadProperties threadProperties;
}
🕰️ 参数说明
- 👉corePoolSize: 核心线程数
核心线程会一直存活,及时没有任务需要执行
当线程数小于核心线程数时,即使有线程空闲,线程池也会优先创建新线程处理
设置allowCoreThreadTimeout=true(默认false)时,核心线程会超时关闭
- 👉maximumPoolSize:最大线程数
当线程数=corePoolSize,且任务队列已满时。线程池会创建新线程来处理任务
当线程数=maxPoolSize,且任务队列已满时,线程池会拒绝处理任务而抛出异常
- 👉keepAliveTime:线程空闲时间
当线程空闲时间达到keepAliveTime时,线程会退出,直到线程数量=corePoolSize
如果allowCoreThreadTimeout=true,则会直到线程数量=0
- 👉queueCapacity:任务队列容量(阻塞队列)
当核心线程数达到最大时,新任务会放在队列中排队等待执行
- 👉rejectedExecutionHandler:任务拒绝处理器
AbortPolicy:丢弃任务,抛运行时异常
CallerRunsPolicy:由调用者线程去执行
DiscardPolicy:直接丢弃任务本身
DiscardOldestPolicy:从队列中踢出最先进入队列(最后一个执行)的任务
🕰️ 创建线程池
现在大多数公司都在遵循阿里巴巴 Java 开发规范,该规范里明确说明不允许使用 Executors 创建线程池,而是通过 ThreadPoolExecutor 显示指定参数去创建
/**
* 创建线程池
*
* @param
* @return
*/
@Bean("threadPoolTaskExecutor")
public ThreadPoolTaskExecutor threadPoolTaskExecutor() {
ThreadPoolTaskExecutor threadPoolTaskExecutor = new ThreadPoolTaskExecutor();
//核心线程池大小
threadPoolTaskExecutor.setMaxPoolSize(threadProperties.getMaxPoolSize());
//最大可创建的线程数
threadPoolTaskExecutor.setCorePoolSize(threadProperties.getCorePoolSize());
//队列最大长度
threadPoolTaskExecutor.setQueueCapacity(threadProperties.getQueueCapacity());
//线程池维护线程所允许的空闲时间
threadPoolTaskExecutor.setKeepAliveSeconds(threadProperties.getKeepAliveSeconds());
//拒绝策略
threadPoolTaskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
return threadPoolTaskExecutor;
}
- ThreadPoolExecutor 来创建线程池,那核心参数设置多少合适呢
Ncpu = CPU 核数
Ucpu = 目标 CPU 利用率,0 <= Ucpu <= 1
W / C = 等待时间 / 计算时间
要程序跑到 CPU 的目标利用率,需要的线程数为:
Nthreads = Ncpu * Ucpu * (1 + W / C)
👉🏽 参考:线程池中各个参数如何合理设置
🕰️ 执行周期性或定时性任务
/**
* 执行周期性或定时性任务
*
* @return
*/
@Bean("scheduledExecutorService")
public ScheduledExecutorService scheduledExecutorService() {
BasicThreadFactory build = new BasicThreadFactory.Builder().namingPattern("schedule-pool-%d").daemon(true).build();
ThreadPoolExecutor.CallerRunsPolicy callerRunsPolicy = new ThreadPoolExecutor.CallerRunsPolicy();
ScheduledThreadPoolExecutor scheduledThreadPoolExecutor =
new ScheduledThreadPoolExecutor(threadProperties.getCorePoolSize(), build, callerRunsPolicy);
return scheduledThreadPoolExecutor;
}
三、异步任务管理器
package com.michale.framework.config.async.manager;
import com.michale.common.utils.spring.SpringUtils;
import io.swagger.annotations.ApiModelProperty;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import javax.annotation.PreDestroy;
import java.util.TimerTask;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
/**
* @Author Michale
* @CreateDate 2022/9/16
* @Describe 异步任务管理器
*/
@Slf4j
@Component
public class AsyncManager {
@ApiModelProperty("操作延迟10毫秒")
private final int OPERATE_DELAY_TIME = 10;
private ScheduledExecutorService executor = SpringUtils.getBean("scheduledExecutorService");
public AsyncManager() {
}
private static AsyncManager annotations = new AsyncManager();
public static AsyncManager asyncManager() {
return annotations;
}
/**
* 执行异步任务
*
* @param timerTask
* @return
* @throws Exception
*/
public void start(TimerTask timerTask) throws Exception {
executor.schedule(timerTask, OPERATE_DELAY_TIME, TimeUnit.MILLISECONDS);
}
/**
* 停止任务线程池
* 确保应用退出时能关闭后台线程(对象销毁时执行)
*
* @throws Exception
*/
@PreDestroy
public void stop() throws Exception {
try {
executor.shutdown();
log.info("====关闭后台任务任务线程池====");
} catch (Exception e) {
log.error(e.getMessage(), e);
}
}
}
- 👉🏽execute() 提交任务和 submit() 提交任务有啥不同
- execute() 无返回值
- submit() 有返回值 |会返回一个 FutureTask,然后可以调用 get() 方法阻塞获取返回值
- 👉🏽自定义关闭线程方法
* 停止线程池
* 先使用shutdown, 停止接收新任务并尝试完成所有已存在任务.
* 如果超时, 则调用shutdownNow, 取消在workQueue中Pending的任务,并中断所有阻塞函数.
* 如果仍然超時,則強制退出.
* 另对在shutdown时线程本身被调用中断做了处理.
public static void shutdownAndAwaitTermination(ExecutorService pool) {
if (pool != null && !pool.isShutdown()) {
pool.shutdown();
try {
if (!pool.awaitTermination(120, TimeUnit.SECONDS)) {
pool.shutdownNow();
if (!pool.awaitTermination(120, TimeUnit.SECONDS)) {
log.info("Pool did not terminate");
}
}
} catch (InterruptedException ie) {
pool.shutdownNow();
Thread.currentThread().interrupt();
}
}
}
👉🏽测试异步任务管理器
/**
* 重写run方法执行任务
*
* @return
*/
public TimerTask test() {
return new TimerTask() {
/**
* The action to be performed by this timer task.
*/
@Override
public void run() {
System.out.println("test");
}
};
}
AsyncManager.asyncManager().start(test());
📢🌥️如果文章对你有帮助【关注👍点赞❤️收藏⭐】