• Spring自带的线程池ThreadPoolTaskExecutor


    https://zhuanlan.zhihu.com/p/346086161

    前言

    上一篇分享了JDK自带的线程池ThreadPoolExecutor的配置和参数详解,然而我们实际开发中更多的是使用SpringBoot来开发,Spring默认也是自带了一个线程池方便我们开发,它就是ThreadPoolTaskExecutor,接下来我们就来聊聊Spring的线程池吧。

    Spring默认线程池simpleAsyncTaskExecutor

    Spring异步线程池的接口类是TaskExecutor,本质还是java.util.concurrent.Executor,没有配置的情况下,默认使用的是simpleAsyncTaskExecutor。

    @Async演示Spring默认的simpleAsyncTaskExecutor

    @Component
    @EnableAsync
    public class ScheduleTask {
    
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    
        @Async
        @Scheduled(fixedRate = 2000)
        public void testScheduleTask() {
            try {
                Thread.sleep(6000);
                System.out.println("Spring1自带的线程池" + Thread.currentThread().getName() + "-" + sdf.format(new Date()));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    
        @Async
        @Scheduled(cron = "*/2 * * * * ?")
        public void testAsyn() {
            try {
                Thread.sleep(1000);
                System.out.println("Spring2自带的线程池" + Thread.currentThread().getName() + "-" + sdf.format(new Date()));
            } catch (Exception ex) {
                ex.printStackTrace();
            }
        }
    }
    

    从运行结果可以看出Spring默认的@Async用线程池名字为SimpleAsyncTaskExecutor,而且每次都会重新创建一个新的线程,所以可以看到TaskExecutor-后面带的数字会一直变大。

    simpleAsyncTaskExecutor的特点是,每次执行任务时,它会重新启动一个新的线程,并允许开发者控制并发线程的最大数量(concurrencyLimit),从而起到一定的资源节流作用。默认是concurrencyLimit取值为-1,即不启用资源节流。

    Spring的线程池ThreadPoolTaskExecutor

    上面介绍了Spring默认的线程池simpleAsyncTaskExecutor,但是Spring更加推荐我们开发者使用ThreadPoolTaskExecutor类来创建线程池,其本质是对java.util.concurrent.ThreadPoolExecutor的包装。

    这个类则是spring包下的,是Spring为我们开发者提供的线程池类,这里重点讲解这个类的用法。

    Spring提供了xml给我们配置ThreadPoolTaskExecutor线程池,但是现在普遍都在用SpringBoot开发项目,所以直接上yaml或者properties配置即可,或者也可以使用@Configuration配置也行,下面演示配置和使用。

    ThreadPoolTaskExecutor配置

    application.properties

    # 核心线程池数
    spring.task.execution.pool.core-size=5
    # 最大线程池数
    spring.task.execution.pool.max-size=10
    # 任务队列的容量
    spring.task.execution.pool.queue-capacity=5
    # 非核心线程的存活时间
    spring.task.execution.pool.keep-alive=60
    # 线程池的前缀名称
    spring.task.execution.thread-name-prefix=god-jiang-task-
    

    AsyncScheduledTaskConfig.java

    @Configuration
    public class AsyncScheduledTaskConfig {
    
        @Value("${spring.task.execution.pool.core-size}")
        private int corePoolSize;
        @Value("${spring.task.execution.pool.max-size}")
        private int maxPoolSize;
        @Value("${spring.task.execution.pool.queue-capacity}")
        private int queueCapacity;
        @Value("${spring.task.execution.thread-name-prefix}")
        private String namePrefix;
        @Value("${spring.task.execution.pool.keep-alive}")
        private int keepAliveSeconds;
        @Bean
        public Executor myAsync() {
            ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
            //最大线程数
            executor.setMaxPoolSize(maxPoolSize);
            //核心线程数
            executor.setCorePoolSize(corePoolSize);
            //任务队列的大小
            executor.setQueueCapacity(queueCapacity);
            //线程前缀名
            executor.setThreadNamePrefix(namePrefix);
            //线程存活时间
            executor.setKeepAliveSeconds(keepAliveSeconds);
    
            /**
             * 拒绝处理策略
             * CallerRunsPolicy():交由调用方线程运行,比如 main 线程。
             * AbortPolicy():直接抛出异常。
             * DiscardPolicy():直接丢弃。
             * DiscardOldestPolicy():丢弃队列中最老的任务。
             */
            executor.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy());
            //线程初始化
            executor.initialize();
            return executor;
        }
    }
    

    在方法上添加@Async注解,然后还需要在@SpringBootApplication启动类或者@Configuration注解类上 添加注解@EnableAsync启动多线程注解,@Async就会对标注的方法开启异步多线程调用,注意,这个方法的类一定要交给Spring容器来管理

    @Component
    @EnableAsync
    public class ScheduleTask {
    
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    
        @Async("myAsync")
        @Scheduled(fixedRate = 2000)
        public void testScheduleTask() {
            try {
                Thread.sleep(6000);
                System.out.println("Spring1自带的线程池" + Thread.currentThread().getName() + "-" + sdf.format(new Date()));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    
        @Async("myAsync")
        @Scheduled(cron = "*/2 * * * * ?")
        public void testAsyn() {
            try {
                Thread.sleep(1000);
                System.out.println("Spring2自带的线程池" + Thread.currentThread().getName() + "-" + sdf.format(new Date()));
            } catch (Exception ex) {
                ex.printStackTrace();
            }
        }
    }
    

    以上从运行结果可以看出,自定义ThreadPoolTaskExecutor可以实现线程的复用,而且还能控制好线程数,写出更好的多线程并发程序。

    另外需要注意的是:关于注解失效需要注意以下几点

    注解的方法必须是public方法
    方法一定要从另一个类中调用,也就是从类的外部调用,类的内部调用是无效的,因为@Transactional和@Async注解的实现都是基于Spring的AOP,而AOP的实现是基于动态代理模式实现的。那么注解失效的原因就很明显了,有可能因为调用方法的是对象本身而不是代理对象,因为没有经过Spring容器。
    异步方法使用注解@Async的返回值只能为void或者Future
    上面注解中已经注释了参数的详解,这里重点讲解一下Spring线程池的拒绝策略和处理流程。

    拒绝策略

    rejectedExectutionHandler参数字段用于配置绝策略,常用拒绝策略如下

    AbortPolicy:用于被拒绝任务的处理程序,它将抛出RejectedExecutionException
    CallerRunsPolicy:用于被拒绝任务的处理程序,它直接在execute方法的调用线程中运行被拒绝的任务。
    DiscardOldestPolicy:用于被拒绝任务的处理程序,它放弃最旧的未处理请求,然后重试execute。
    DiscardPolicy:用于被拒绝任务的处理程序,默认情况下它将丢弃被拒绝的任务。
    处理流程
    查看核心线程池是否已满,不满就创建一条线程执行任务,否则执行第二步。
    查看任务队列是否已满,不满就将任务存储在任务队列中,否则执行第三步。
    查看线程池是否已满,即就是是否达到最大线程池数,不满就创建一条线程执行任务,否则就按照策略处理无法执行的任务。

    总结

    这次分享了Spring自带的线程池ThreadPoolTaskExecutor的配置和使用,并且讲了线程池的参数和处理流程。当然Spring提供了7个线程池的实现,感兴趣的可以自行了解~~~

  • 相关阅读:
    Unicode详解
    VC++ 6.0 中如何使用 CRT 调试功能来检测内存泄漏
    关于MFC下检查和消除内存泄露的技巧
    ASCII 、GB2312、GBK、GB18030、unicode、UTF8字符集编码详解
    A note from UrlEscape Function
    Unicode和UTF8之间的转换详解
    Base64编码
    全面解读WM_NOTIFY
    关于URL编码
    URL的#号
  • 原文地址:https://www.cnblogs.com/zhaoyuan72/p/16347289.html
Copyright © 2020-2023  润新知