ThreadPoolConfig线程池配置

package com.itmc.configuration;

import org.apache.commons.lang3.concurrent.BasicThreadFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;

import java.util.concurrent.*;

/**
 * @Author: yhf
 * @Date: 2026/4/13 9:33
 */
@Configuration
//@EnableAsync //@EnableAsync放在启动类(推荐)或配置类上,功能完全相同,都能开启异步。但是放在配置类时加载顺序晚于启动类,极端情况下会导致异步失效(比如配置类加载失败、被依赖影响)。
public class ThreadPoolConfig {

    private static final Logger logger = LoggerFactory.getLogger(ThreadPoolConfig.class);

    // 核心线程池大小
    private int corePoolSize = 50;

    // 最大可创建的线程数
    private int maxPoolSize = 200;

    // 队列最大长度
    private int queueCapacity = 1000;

    // 线程池维护线程所允许的空闲时间
    private int keepAliveSeconds = 300;

    //默认异步线程池:service层方法上+@Async或@Async("taskExecutor")
    @Bean(name = "taskExecutor") //taskExecutor是Spring @Async默认查找的Bean名称,此处一旦定义,所有没指定名称的@Async都会用它
    public Executor taskExecutor() {
        ThreadPoolTaskExecutor poolTaskExecutor = new ThreadPoolTaskExecutor();

        /*// 获取CPU核心数
        int core = Runtime.getRuntime().availableProcessors();
        // 设置核心线程数
        poolTaskExecutor.setCorePoolSize(core - 1); //核心线程根据CPU核数动态设置,IO密集型常用
        // 设置最大线程数
        poolTaskExecutor.setMaxPoolSize(2 * core + 1);
        // 允许线程的空闲时间,超过了核心线程数之外的线程,在空闲时间到达后会被销毁
        poolTaskExecutor.setKeepAliveSeconds(60);
        // 传入值大于1,底层队列使用的是LinkedBlockingQueue,默认为SynchronousQueue
        poolTaskExecutor.setQueueCapacity(40);*/
        // ======== 核心配置(优化后)========
        poolTaskExecutor.setMaxPoolSize(20);                // 最大20足够支撑高并发(峰值安全扩容)
        poolTaskExecutor.setCorePoolSize(10);               // IO密集型固定10,更稳定(业务异步任务常驻足够线程,不频繁创建)
        poolTaskExecutor.setQueueCapacity(1000);            // 队列放大,避免频繁扩容(抗流量冲击,不轻易触发最大线程)
        poolTaskExecutor.setKeepAliveSeconds(60);           // 空闲等待60秒

        // 设置线程名称前缀,日志方便排查
        poolTaskExecutor.setThreadNamePrefix("DefaultAsync-");

        // 设置拒绝策略
        // rejection-policy:当pool已经达到max size的时候,如何处理新任务
        // CALLER_RUNS:不在新线程中执行任务,而是由调用者所在的线程来执行
        poolTaskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); //不丢任务、不抛异常

        //优雅停机-->服务重启不丢任务
        // 优雅停机设置1:设置线程池关闭的时候等待所有任务执行完毕在继续销毁
        poolTaskExecutor.setWaitForTasksToCompleteOnShutdown(true);
        // 优雅停机设置2:线程池中的任务的等待时间,如果超出时间没有销毁就强制销毁,以确保应用最后能够被关闭,而不是阻塞住
        poolTaskExecutor.setAwaitTerminationSeconds(60);

        // 初始化
        poolTaskExecutor.initialize();
        return poolTaskExecutor;
    }
    /*
    @Bean
    public Executor taskExecutor() { ... }

    @Bean(name = "taskExecutor")
    public Executor anyName() { ... }

    上述两者效果一样
    @Bean没写name时,Spring会自动把方法名taskExecutor作为Bean的名字
    @Bean指定name时,方法名写啥都行,@Async会根据指定的name去找线程池
     */

    //业务自定义线程池:service层方法上+@Async("threadPoolTaskExecutor")
    @Bean(name = "threadPoolTaskExecutor")
    public ThreadPoolTaskExecutor threadPoolTaskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setMaxPoolSize(maxPoolSize); //最大可创建的线程数=200   [最大线程数,峰值时可创建]
        executor.setCorePoolSize(corePoolSize); // 核心线程池大小=50    [核心线程数,常驻线程]
        executor.setQueueCapacity(queueCapacity); // 队列最大长度=1000  [任务队列容量]
        executor.setKeepAliveSeconds(keepAliveSeconds); // 线程池维护线程所允许的空闲时间=300   [非核心线程空闲存活时间(秒)]
        executor.setThreadNamePrefix("CustomAsync-"); //线程名称前缀,便于日志追踪
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); //拒绝策略:调用者线程执行
        /*
            工作流程:新任务提交 → 核心线程(50) → 队列(1000) → 最大线程(200) → 拒绝策略

            线程池对拒绝任务(无线程可用)的处理策略: ThreadPoolExecutor.CallerRunsPolicy()
            不丢弃任务:不允许任务丢失,但可以接受任务处理延迟。确保提交的任务最终会被执行。
            不抛异常:不会触发 RejectedExecutionException
            背压效应:会降低任务提交的速度,因为调用者线程被阻塞去执行任务了。希望借此减缓任务提交速度,避免系统过载。

            其他常见策略对比
                AbortPolicy (默认):直接抛出 RejectedExecutionException 异常。
                DiscardPolicy:直接丢弃新任务,不抛异常。
                DiscardOldestPolicy:丢弃队列中最老的任务,尝试重新提交新任务。
        */

//        executor.setWaitForTasksToCompleteOnShutdown(true); //优雅关闭配置1
//        executor.setAwaitTerminationSeconds(60*3); //优雅关闭配置2
//        executor.initialize(); //确保线程池正确初始化
        return executor;
    }

    //资源隔离线程池:service层方法上+@Async("threadPoolResourceExchange")
    //配置多个独立@Async线程池 --> 业务隔离,不互相拖垮
    @Bean(name = "threadPoolResourceExchange")
    public ThreadPoolTaskExecutor threadPoolResourceExchange() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setMaxPoolSize(20);
        executor.setCorePoolSize(4);
        executor.setQueueCapacity(200);
        executor.setKeepAliveSeconds(keepAliveSeconds);
        executor.setThreadNamePrefix("ResourceAsync-");
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        return executor;
    }

    //调度器执行周期、定时、延迟任务(方案1:使用JDK原生)
    //[如果你依赖 JDK 原生的 ScheduledExecutorService 接口,应继续使用 ScheduledThreadPoolExecutor。]
    @Bean(name = "scheduledExecutorService")
    protected ScheduledExecutorService scheduledExecutorService() {
        return new ScheduledThreadPoolExecutor(corePoolSize,
                new BasicThreadFactory.Builder().namingPattern("schedule-poolJDK-%d").daemon(true).build())
        {
            @Override
            protected void afterExecute(Runnable r, Throwable t)
            {
                super.afterExecute(r, t);
                printException(r, t);
            }
        };
        /*
        核心线程数	corePoolSize	需确保该变量已定义,决定常驻线程数
        线程命名	schedule-pool-%d	便于日志追踪和问题排查
        守护线程	daemon(true)	注意:JVM 退出时会强制终止,可能导致任务中断
        异常处理	afterExecute	重写该方法通常用于捕获任务执行中的异常
        */
    }

    //调度器执行周期、定时、延迟任务(方案2:使用Spring封装)
    @Bean(name = "scheduledExecutorService2")
    protected ThreadPoolTaskScheduler scheduledExecutorService2() {
        ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
        scheduler.setPoolSize(corePoolSize);
        scheduler.setThreadNamePrefix("schedule-poolSpring-");
        scheduler.setDaemon(false); // 关键任务建议非守护线程,防止任务意外中断。true时JVM关闭直接杀任务,false时任务不执行完,JVM不会强制关闭。
       /*scheduler.setAwaitTerminationSeconds(60);
        scheduler.setWaitForTasksToCompleteOnShutdown(true);
        scheduler.initialize();*/
        scheduler.setErrorHandler(t -> logger.error("定时/延迟任务执行异常", t)); // 如果需要全局异常处理,可配置 TaskErrorHandler
        return scheduler;
    }
    /*
    scheduledExecutorService2 = 万能任务调度器,四种用途:
    1. 固定频率重复执行
        // 每 10 秒执行一次(固定频率)
        scheduledExecutorService2.scheduleAtFixedRate(() -> {
            // 循环执行的业务
        }, 0, 10, TimeUnit.SECONDS);
    2. 固定间隔重复执行
        // 上一次执行完 → 等 10 秒 → 再执行
        scheduledExecutorService2.scheduleWithFixedDelay(() -> {
            // 业务逻辑
        }, 0, 10, TimeUnit.SECONDS);
    3. 动态定时任务
        // 指定未来某个时间执行
        Date futureTime = ...; // 比如晚上8点
        scheduledExecutorService2.schedule(() -> {
            // 到时执行
        }, futureTime);
    4. 取消任务
        ScheduledFuture<?> future = scheduledExecutorService2.schedule(..., time);
        future.cancel(false); // 不想执行了,直接取消

        @Autowired
        private ThreadPoolTaskScheduler taskScheduler;
        直接自动注入默认的 taskScheduler:
            池大小 = 1(单线程)
            线程名 = 系统默认
            只能处理少量延迟任务
            并发高一点 就会排队、阻塞、延迟不准

        @Autowired
        @Qualifier("scheduledExecutorService2") // 强制指定用你配置的Bean → 保险
        private ThreadPoolTaskScheduler scheduledExecutorService2; // 变量名 = Bean名 → 精准匹配
        自己配置(scheduledExecutorService2):
            池大小 你自己设(比如 50)
            线程名 你自己定义
            守护线程、异常处理 你控制
            适合大量延迟任务、生产环境
     */

    /**
     * 打印线程异常信息
     */
    public static void printException(Runnable r, Throwable t)
    {
        if (t == null && r instanceof Future<?>)
        {
            try
            {
                Future<?> future = (Future<?>) r;
                if (future.isDone())
                {
                    future.get();
                }
            }
            catch (CancellationException ce)
            {
                t = ce;
            }
            catch (ExecutionException ee)
            {
                t = ee.getCause();
            }
            catch (InterruptedException ie)
            {
                Thread.currentThread().interrupt();
            }
        }
        if (t != null)
        {
            logger.error(t.getMessage(), t);
        }
    }


    //@Async+Thread.sleep()=线程池杀手,会直接毁掉异步线程池的性能。(本地调试可以临时用,生产环境严禁出现)
    //原因:线程池里的线程是复用的,不是无限创建。用sleep()会让线程空占着不干活,阻塞整个线程池。
    //结果:线程池很快被占满,新的异步任务全部排队或卡住。服务吞吐量暴跌,甚至引发任务堆积或超时。
    //解决:类@Autowired private ThreadPoolTaskScheduler taskScheduler;方法taskScheduler.schedule(()->{业务逻辑}, new Date(System.currentTimeMillis()+延迟时间毫秒));
    //或者使用自定义bean更好 类@Autowired private ThreadPoolTaskScheduler scheduledExecutorService2;方法scheduledExecutorService2.schedule(()->{业务逻辑}, new Date(System.currentTimeMillis()+延迟时间毫秒));
    //不阻塞@Async线程,等待期间线程直接放回线程池,延迟靠调度器ThreadPoolTaskScheduler实现,零资源浪费,生产环境安全。


    //@Async + @Transactional 同时标在同一个方法上 = 事务失效!
    //事务只在当前线程生效,异步会新开线程,线程之间不共享事务!
    //正确写法:异步方法调用内部带事务的同步方法!
    /*
    @Service
    public class YourService {

        // ======================
        // 异步入口:只负责异步
        // ======================
        @Async("taskExecutor")
        public void doAsync() {
            // 调用内部事务方法
            this.doRealBiz();
        }

        // ======================
        // 同步方法:只负责事务
        // ======================
        @Transactional(rollbackFor = Exception.class)
        public void doRealBiz() {
            // 数据库增删改 → 事务正常生效
        }
    }
     */
    //不要在 @Transactional 里直接调用 @Async!
    //会导致:异步方法读不到未提交数据,数据不一致,业务异常。【事务未提交 → 异步方法已执行 → 脏读、不可重复读】
    //推荐等事务提交完成之后,再调用异步方法!


}

posted @ 2026-04-13 09:39  yub4by  阅读(3)  评论(0)    收藏  举报