SpringBoot中使用异步调度程序的高级方法

 更新时间:2024年07月04日 09:45:24   作者:Eddie_920  
本文主要介绍了SpringBoot中使用异步调度程序的高级方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

案例:

有两个调度器,一个调度器每3分钟运行一次从数据库中获取数据,另一个调度器每分钟运行一次调用REST API。

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableAsync;

@SpringBootApplication
public class YourSpringBootApplication {

    public static void main(String[] args) {
        SpringApplication.run(YourSpringBootApplication.class, args);
    }
}

import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

@Component
public class FirstScheduler {

    @Scheduled(fixedRate = 3 * 60 * 1000) // 每3分钟运行一次
    public void getDataFromDB() {
        // 从数据库获取数据的代码
    }
}

import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

@Component
public class SecondScheduler {

    @Scheduled(fixedRate = 1 * 60 * 1000) // 每1分钟运行一次
    public void consumeRestApi() {
        // 调用REST API的代码
    }
}

问题:

当第一个调度器运行时间超过预期时间,例如执行了10分钟,那么第二个调度器则不会运行,一直到第一个调度器运行结束后。

上述代码存在的问题,当一个调度器运行时,第二个调度器会被阻塞。

解决方案:

为了避免第二个调度器因第一个调度器而被阻塞,可以使用Spring的异步执行。这样做每个调度器都将在自己单独线程中运行从而实现独立工作。

实现代码:

在使用Spring的异步执行支持时,最佳做法是配置一个自定义线程池来控制异步任务执行所用的线程数量。默认情况下Spring使用SimpleAsyncTaskExecutor(这在生产环境中可能不太合适,因为它不提供更多对线程池的控制方法。)
为了解决这个问题建议在应用程序中创建一个自定义线程池bean。以下是实现代码:

步骤1:定义一个配置类创建自定义线程池bean。

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

@Configuration
@EnableAsync
public class AsyncConfiguration {

    @Bean(name = "asyncTaskExecutor")
    public ThreadPoolTaskExecutor asyncTaskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(5); // 设置线程池中的初始线程数
        executor.setMaxPoolSize(10); // 设置线程池中的最大线程数
        executor.setQueueCapacity(25); // 设置用于保存挂起任务的队列容量
        executor.setThreadNamePrefix("AsyncTask-"); // 设置线程名前缀
        executor.initialize();
        return executor;
    }
}

步骤2:修改第一个调度器,使其使用此自定义线程池。

import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

@Component
public class FirstScheduler {

    @Async("asyncTaskExecutor") // 指定自定义线程池bean名称
    @Scheduled(fixedRate = 3 * 60 * 1000) // 每3分钟运行一次
    public void getDataFromDB() {
        // 从数据库获取数据的代码
        // 此方法将在自定义线程池中异步运行
    }
}

通过这种配置,第一个调度器方法(getDataFromDB)将在自定义线程池中异步运行,而第二个调度器方法(consumeRestApi)则在默认调度器的线程中运行。

根据应用程序需求和可用系统资源调整corePoolSize、maxPoolSize和queueCapacity值。线程池配置对应用程序的性能产生比较重要的影响,因此需要适当调整这些值。

要使第二个调度器也使用自定义线程池进行异步执行,需要在consumeRestApi方法的@Async注解中添加taskExecutor属性。这样确保两个调度器都在相同的自定义线程池中异步运行。以下是更新后的代码:

import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

@Component
public class SecondScheduler {

    @Async("asyncTaskExecutor") // 指定自定义线程池bean名称
    @Scheduled(fixedRate = 1 * 60 * 1000) // 每1分钟运行一次
    public void consumeRestApi() {
        // 调用REST API的代码
        // 此方法将在自定义线程池中异步运行
    }
}

通过这种设置,第一个调度器(getDataFromDB)和第二个调度器(consumeRestApi)都将在相同的自定义线程池中异步运行。这将允许它们独立地工作,即使其中一个任务需要更长时间来完成。

使用自定义线程池
记录错误消息,当所需线程池大小 > 配置的线程池大小时:
要在所需线程池大小超过配置的线程池大小时记录错误消息,可以利用Spring的ThreadPoolTaskExecutor的RejectedExecutionHandler。当线程池的任务队列已满且线程池无法接受更多任务时,将调用此处理程序。可以使用此回调来记录错误消息。

以下是更新的配置类,其中包含RejectedExecutionHandler:

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 java.util.concurrent.ThreadPoolExecutor;

@Configuration
@EnableAsync
@Sl4J
public class AsyncConfiguration {

    @Bean(name = "asyncTaskExecutor")
    public ThreadPoolTaskExecutor asyncTaskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(5); // 设置线程池中的初始线程数
        executor.setMaxPoolSize(10); // 设置线程池中的最大线程数
        executor.setQueueCapacity(25); // 设置用于保存挂起任务的队列容量
        executor.setThreadNamePrefix("AsyncTask-"); // 设置线程名前缀

        // 设置RejectedExecutionHandler以记录错误消息
        executor.setRejectedExecutionHandler((Runnable r, ThreadPoolExecutor e) -> {
            // 记录错误消息,队列已满,任务被拒绝
            // 根据需要自定义此消息
            log.error("任务被拒绝:线程池已满。请增加线程池大小。");
        });

        executor.initialize();
        return executor;
    }
}

通过这种配置,当线程池的队列已满且尝试提交额外任务时,RejectedExecutionHandler会被触发。
你可以根据应用程序的需求自定义错误消息或采取其他操作。

请注意,设置正确的线程池大小和队列容量对于应用程序的性能和资源利用率至关重要。

如果因为队列容量不足而任务持续被拒绝则需要增加线程池大小或调整队列容量。

总结:

通过配置共享的自定义线程池、利用@Async注解以及整合RejectedExecutionHandler,可以使应用程序有效地管理和执行多个调度器并发从而确保调度器独立运行,并且系统能够优雅地响应线程池限制。

到此这篇关于SpringBoot中使用异步调度程序的高级方法的文章就介绍到这了,更多相关SpringBoot 异步调度内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Java使用BIO和NIO进行文件操作对比代码示例

    Java使用BIO和NIO进行文件操作对比代码示例

    这篇文章主要介绍了Java使用BIO和NIO进行文件操作对比代码示例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-05-05
  • Spring使用hutool的HttpRequest发送请求的几种方式

    Spring使用hutool的HttpRequest发送请求的几种方式

    Spring HttpRequest是指Spring框架中的一个对象,它代表了HTTP客户端发送给Web服务器的一次请求,本文给大家介绍了Spring使用hutool的HttpRequest发送请求的几种方式,并通过代码示例讲解的非常详细,需要的朋友可以参考下
    2024-11-11
  • Java静态泛型使用方法实例解析

    Java静态泛型使用方法实例解析

    这篇文章主要介绍了Java静态泛型使用方法实例解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-06-06
  • SpringBoot使用AOP统一日志管理的方法详解

    SpringBoot使用AOP统一日志管理的方法详解

    这篇文章主要为大家分享一个干货:超简洁SpringBoot使用AOP统一日志管理,文中的示例代码讲解详细,感兴趣的小伙伴快跟随小编一起学习学习吧
    2022-05-05
  • spring 中事务注解@Transactional与trycatch的使用

    spring 中事务注解@Transactional与trycatch的使用

    这篇文章主要介绍了spring 中事务注解@Transactional与trycatch的使用,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-06-06
  • SpringBoot设置编码UTF-8的两种方法

    SpringBoot设置编码UTF-8的两种方法

    本文通过两种方式给大家介绍SpringBoot 设置编码UTF-8 ,每种方式通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2021-11-11
  • WebUploader+SpringMVC实现文件上传功能

    WebUploader+SpringMVC实现文件上传功能

    WebUploader是由Baidu团队开发的一个简单的以HTML5为主,FLASH为辅的现代文件上传组件。这篇文章主要介绍了WebUploader+SpringMVC实现文件上传功能,需要的朋友可以参考下
    2017-06-06
  • Java如何读写Properties配置文件(Properties类)

    Java如何读写Properties配置文件(Properties类)

    这篇文章主要介绍了Java如何读写Properties配置文件(Properties类),具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2023-05-05
  • 最简单的spring boot打包docker镜像的实现

    最简单的spring boot打包docker镜像的实现

    这篇文章主要介绍了最简单的spring boot打包docker镜像的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-10-10
  • java 反射和动态代理详解及实例代码

    java 反射和动态代理详解及实例代码

    这篇文章主要介绍了java 反射和动态代理详解及实例代码的相关资料,需要的朋友可以参考下
    2016-09-09

最新评论