第十一章🚦异步任务-线程池配置

1,091 阅读4分钟

🧑‍🎓 个人主页:沉默的羔羊

📖 本章内容:【异步任务-线程池配置

image.png

🌳Gitee仓库地址:👉🏽线程池配置

前言

  • 👉🏽使用线程池可以带来以下好处
- 降低资源消耗。降低频繁创建、销毁线程带来的额外开销,复用已创建线程
- 降低使用复杂度。将任务的提交和执行进行解耦
- 我们只需要创建一个线程池,然后往里面提交任务就行
- 具体执行流程由线程池自己管理,降低使用复杂度
- 提高线程可管理性。能安全有效的管理线程资源,避免不加限制无限申请造成资源耗尽风险
- 提高响应速度。任务到达后,直接复用已创建好的线程执行
  • 👉🏽ThreadPoolExecutor 都有哪些核心参数
核心线程数(corePoolSize)
最大线程数(maximumPoolSize)
空闲线程超时时间(keepAliveTime)
时间单位(unit)
阻塞队列(workQueue)
拒绝策略(handler)
线程工厂(ThreadFactory
  • 👉🏽任务拒绝策略
AbortPolicy:丢弃任务,抛运行时异常
CallerRunsPolicy:由调用者线程去执行
DiscardPolicy:直接丢弃任务本身
DiscardOldestPolicy:从队列中踢出最先进入队列(最后一个执行)的任务
  • 👉🏽线程池的执行流程
  1. 判断线程池的状态,如果不是RUNNING状态,直接执行拒绝策略
  2. 如果 当前线程数 < 核心线程池,则新建一个线程来处理提交的任务
  3. 如果 当前线程数 > 核心线程数且任务队列没满,则将任务放入阻塞队列等待执行
  4. 如果 核心线程池 < 当前线程池数 < 最大线程数且任务队列已满,则创建新的线程执行提交的任务
  5. 如果 当前线程数 > 最大线程数且队列已满,则执行拒绝策略拒绝该任务

一、基础配置

🧭 引入依赖

<!-- 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() 提交任务有啥不同
  1. execute() 无返回值
  2. 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());

扫码_搜索联合传播样式-标准色版.png

📢🌥️如果文章对你有帮助【关注👍点赞❤️收藏⭐】