温馨提示×

温馨提示×

您好,登录后才能下订单哦!

密码登录×
登录注册×
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》
  • 首页 > 
  • 教程 > 
  • 开发技术 > 
  • Spring中怎么使用ThreadPoolTaskExecutor自定义线程池及异步调用

Spring中怎么使用ThreadPoolTaskExecutor自定义线程池及异步调用

发布时间:2022-02-25 15:45:53 来源:亿速云 阅读:413 作者:iii 栏目:开发技术

这篇文章主要介绍了Spring中怎么使用ThreadPoolTaskExecutor自定义线程池及异步调用的相关知识,内容详细易懂,操作简单快捷,具有一定借鉴价值,相信大家阅读完这篇Spring中怎么使用ThreadPoolTaskExecutor自定义线程池及异步调用文章都会有所收获,下面我们一起来看看吧。

    多线程一直是工作或面试过程中的高频知识点,今天给大家分享一下使用 ThreadPoolTaskExecutor 来自定义线程池和实现异步调用多线程。

    一、ThreadPoolTaskExecutor

    本文采用 Executors 的工厂方法进行配置。

    1、将线程池用到的参数定义到配置文件中

    在项目的 resources 目录下创建 executor.properties 文件,并添加如下配置:

    # 异步线程配置
    # 核心线程数
    async.executor.thread.core_pool_size=5
    # 最大线程数
    async.executor.thread.max_pool_size=8
    # 任务队列大小
    async.executor.thread.queue_capacity=2
    # 线程池中线程的名称前缀
    async.executor.thread.name.prefix=async-service-
    # 缓冲队列中线程的空闲时间
    async.executor.thread.keep_alive_seconds=100

    2、Executors 的工厂配置

    2.1、配置详情
    @Configuration
    // @PropertySource是找的target目录下classes目录下的文件,resources目录下的文件编译后会生成在classes目录
    @PropertySource(value = {"classpath:executor.properties"}, ignoreResourceNotFound=false, encoding="UTF-8")
    @Slf4j
    public class ExecutorConfig {
        @Value("${async.executor.thread.core_pool_size}")
        private int corePoolSize;
        @Value("${async.executor.thread.max_pool_size}")
        private int maxPoolSize;
        @Value("${async.executor.thread.queue_capacity}")
        private int queueCapacity;
        @Value("${async.executor.thread.name.prefix}")
        private String namePrefix;
        @Value("${async.executor.thread.keep_alive_seconds}")
        private int keepAliveSeconds;
        @Bean(name = "asyncTaskExecutor")
        public ThreadPoolTaskExecutor taskExecutor() {
            log.info("启动");
            ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
            // 核心线程数
            executor.setCorePoolSize(corePoolSize);
            // 最大线程数
            executor.setMaxPoolSize(maxPoolSize);
            // 任务队列大小
            executor.setQueueCapacity(queueCapacity);
            // 线程前缀名
            executor.setThreadNamePrefix(namePrefix);
            // 线程的空闲时间
            executor.setKeepAliveSeconds(keepAliveSeconds);
            // 拒绝策略
            executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
            // 线程初始化
            executor.initialize();
            return executor;
        }
    }
    2.2、注解说明
    • @Configuration:Spring 容器在启动时,会加载带有 @Configuration 注解的类,对其中带有 @Bean 注解的方法进行处理。

    • @Bean:是一个方法级别上的注解,主要用在 @Configuration 注解的类里,也可以用在 @Component 注解的类里。添加的 bean 的 id 为方法名。

    • @PropertySource:加载指定的配置文件。value 值为要加载的配置文件,ignoreResourceNotFound 意思是如果加载的文件找不到,程序是否忽略它。默认为 false 。如果为 true ,则代表加载的配置文件不存在,程序不报错。在实际项目开发中,最好设置为 false 。如果 application.properties 文件中的属性与自定义配置文件中的属性重复,则自定义配置文件中的属性值被覆盖,加载的是 application.properties 文件中的配置属性。

    • @Slf4j:lombok 的日志输出工具,加上此注解后,可直接调用 log 输出各个级别的日志。

    • @Value:调用配置文件中的属性并给属性赋予值。

    2.3、线程池配置说明
    • 核心线程数:线程池创建时候初始化的线程数。当线程数超过核心线程数,则超过的线程则进入任务队列。

    • 最大线程数:只有在任务队列满了之后才会申请超过核心线程数的线程。不能小于核心线程数。

    • 任务队列:线程数大于核心线程数的部分进入任务队列。如果任务队列足够大,超出核心线程数的线程不会被创建,它会等待核心线程执行完它们自己的任务后再执行任务队列的任务,而不会再额外地创建线程。举例:如果有20个任务要执行,核心线程数:10,最大线程数:20,任务队列大小:2。则系统会创建18个线程。这18个线程有执行完任务的,再执行任务队列中的任务。

    • 线程的空闲时间:当 线程池中的线程数量 大于 核心线程数 时,如果某线程空闲时间超过 keepAliveTime ,线程将被终止。这样,线程池可以动态的调整池中的线程数。

    • 拒绝策略:如果(总任务数 - 核心线程数 - 任务队列数)-(最大线程数 - 核心线程数)> 0 的话,则会出现线程拒绝。举例:( 12 - 5 - 2 ) - ( 8 - 5 ) > 0,会出现线程拒绝。线程拒绝又分为 4 种策略,分别为:

    • CallerRunsPolicy():交由调用方线程运行,比如 main 线程。

    • AbortPolicy():直接抛出异常。

    • DiscardPolicy():直接丢弃。

    • DiscardOldestPolicy():丢弃队列中最老的任务。

    2.4、线程池配置个人理解
    • 当一个任务被提交到线程池时,首先查看线程池的核心线程是否都在执行任务。如果没有,则选择一条线程执行任务。

    • 如果都在执行任务,查看任务队列是否已满。如果不满,则将任务存储在任务队列中。核心线程执行完自己的任务后,会再处理任务队列中的任务。

    • 如果任务队列已满,查看线程池(最大线程数控制)是否已满。如果不满,则创建一条线程去执行任务。如果满了,就按照策略处理无法执行的任务。

    二、异步调用线程

    通常 ThreadPoolTaskExecutor 是和 @Async 一起使用。在一个方法上添加 @Async 注解,表明是异步调用方法函数。

    @Async 后面加上线程池的方法名或 bean 名称,表明异步线程会加载线程池的配置。

    @Component
    @Slf4j
    public class ThreadTest {
        /**
         * 每10秒循环一次,一个线程共循环10次。
         */
        @Async("asyncTaskExecutor")
        public void ceshi3() {
            for (int i = 0; i <= 10; i  ) {
                log.info("ceshi3: "   i);
                try {
                    Thread.sleep(2000 * 5);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    备注:一定要在启动类上添加 @EnableAsync 注解,这样 @Async 注解才会生效。

    三、多线程使用场景

    1、定时任务 @Scheduled

    // 在启动类上添加 @EnableScheduling 注解
    @SpringBootApplication
    @EnableScheduling
    public class SpringBootStudyApplication {
       public static void main(String[] args) {
          SpringApplication.run(SpringBootStudyApplication.class, args);
       }
    }
    // @Component 注解将定时任务类纳入 spring bean 管理。
    @Component
    public class listennerTest3 {
        @Autowired
        private ThreadTest t;
        
        // 每1分钟执行一次ceshi3()方法
        @Scheduled(cron = "0 0/1 * * * ?")
        public void run() {
            t.ceshi3();
        }
    }

    ceshi3() 方法调用线程池配置,且异步执行。

    @Component
    @Slf4j
    public class ThreadTest {
        /**
         * 每10秒循环一次,一个线程共循环10次。
         */
        @Async("asyncTaskExecutor")
        public void ceshi3() {
            for (int i = 0; i <= 10; i  ) {
                log.info("ceshi3: "   i);
                try {
                    Thread.sleep(2000 * 5);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    2、程序一启动就异步执行多线程

    通过继承 CommandLineRunner 类实现。

    @Component
    public class ListennerTest implements CommandLineRunner {
        @Autowired
        private ThreadTest t;
        @Override
        public void run(String... args) {
            for (int i = 1; i <= 10; i  ) {
                t.ceshi();
            }
        }
    }
    @Component
    @Slf4j
    public class ThreadTest {
        @Async("asyncTaskExecutor")
        public void ceshi() {
            log.info("ceshi");
        }
    }

    3、定义一个 http 接口

    还可以通过接口的形式来异步调用多线程:

    @RestController
    @RequestMapping("thread")
    public class ListennerTest2 {
        @Autowired
        private ThreadTest t;
        @GetMapping("ceshi2")
        public void run() {
            for (int i = 1; i < 10; i  ) {
                t.ceshi2();
            }
        }
    }
    @Component
    @Slf4j
    public class ThreadTest {
        @Async("asyncTaskExecutor")
        public void ceshi2() {
            for (int i = 0; i <= 3; i  ) {
                log.info("ceshi2");
            }
        }
    }

    4、测试类

    @RunWith(SpringRunner.class)
    @SpringBootTest
    public class ThreadRunTest {
        @Autowired
        private ThreadTest t;
        @Test
        public void thread1() {
            for (int i = 1; i <= 10; i  ) {
                t.ceshi4();
            }
        }
    }
    @Component
    @Slf4j
    public class ThreadTest {
        @Async("asyncTaskExecutor")
        public void ceshi4() {
            log.info("ceshi4");
        }
    }

    关于“Spring中怎么使用ThreadPoolTaskExecutor自定义线程池及异步调用”这篇文章的内容就介绍到这里,感谢各位的阅读!相信大家对“Spring中怎么使用ThreadPoolTaskExecutor自定义线程池及异步调用”知识都有一定的了解,大家如果还想学习更多知识,欢迎关注亿速云行业资讯频道。

    向AI问一下细节

    免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

    AI