当前位置: 首页 > 知识库问答 >
问题:

使用Spring@计划的问题

商宏爽
2023-03-14

我在我的项目中有三个方法用@调度注释,其中一个是cron表达式,另外两个是固定延迟。注释如下所示:

方法1:

@Scheduled(fixedDelay = 20000)
@Async
protected void checkBrokenEngines() {

方法2:

@Scheduled(fixedRate = 20000)
@Async
public void checkAvailableTasks() throws Exception {

方法3:

@Scheduled(cron = "0 0 2 * * ?")
protected void deleteOldData() {

以前,我有一个问题,当check BrokenEnginescheck可用性任务方法执行缓慢时,下一次执行直到上一次结束才发生。从StackOverflow读取留档和这里的一些主题,我看到我的项目有一些错误的池大小设置,并且这些方法没有用异步注释。(异步是为了下一次执行开始,即使旧的没有结束,因为这不会在我的应用程序中引起任何问题)

现在又出现了另一个问题,就是我的问题:

当执行deleteOldData()方法时,其他两个方法在完成之前都不会运行。看到此方法阻止了其他两个方法的执行后,我将此方法注释为异步,然后,即使此方法需要时间才能执行,也会在规定的时间内正确调用其他两个方法。为什么?在我的理解中,这不应该发生,因为这些方法是用async标记的,并且池有足够的空间来执行它们。

PS:deleteOldData()不能异步。它必须在前一次执行完成后立即启动。

编辑1:

异步执行器配置:

@Override    
public AsyncTaskExecutor getAsyncExecutor() {
    log.debug("Creating Async Task Executor");
    ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
    executor.setCorePoolSize(50);
    executor.setMaxPoolSize(50);
    executor.setQueueCapacity(10000);        
    return new ExceptionHandlingAsyncTaskExecutor(executor);
}

共有3个答案

傅自明
2023-03-14

之所以会发生这种情况,是因为@异步任务是由默认的调度执行器提交的,其大小默认为1。

我修改了AsyncTaskExecutor的提交方法:

  @Bean
    AsyncConfigurer asyncConfigurer() {
        return new AsyncConfigurer() {
            @Override
            public AsyncTaskExecutor getAsyncExecutor() {
                ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(){
                    @Override
                    public <T> Future<T> submit(Callable<T> task) {
                        System.out.println("async task was started by thread -- "+Thread.currentThread().getName());
                        return super.submit(task);
                    }
                };
                executor.setThreadNamePrefix("custom-async-exec");
                executor.setCorePoolSize(2);
                executor.setQueueCapacity(100);
                executor.initialize();
                return executor;
            }
        };
    } 

和输出。

async task was started by thread -- scheduling-1
async task was started by thread -- scheduling-1
async task was started by thread -- scheduling-1
async task was started by thread -- scheduling-1
async task was started by thread -- scheduling-1
async task was started by thread -- scheduling-1
async task was started by thread -- scheduling-1
async task was started by thread -- scheduling-1
async task was started by thread -- scheduling-1
async task was started by thread -- scheduling-1

因此,默认调度程序池中有1个线程调度-1,当其繁忙时无法启动/提交新的异步任务。定义Bean线程池TaskExecutor或添加spring。任务行程安排。水塘尺寸=x。

编辑

以下是可视化的简单测试:

@Component
    public static class Jobs{
        @Scheduled(fixedDelay = 1500)
        @Async
        public void job1(){
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        @Scheduled(fixedDelay = 1500)
        @Async
        public void job2(){
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        @Scheduled(initialDelay = 10000, fixedDelay = 5000)
        public void blocking(){
            try {
                Thread.sleep(10000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

可视化来自visualvm

红色“箭头”显示作业开始的点。虽然调度-1线程被阻止,但也无法提交job1()和job2()

闻人宏盛
2023-03-14

计划的任务由ThreadPoolTaskScheduler处理,它的默认池大小为1。只有当它们被注释为@Async时,执行才会传递到AsyncTaskExecator,它为您配置了一个具有更大池大小的专用执行程序。

要在配置类中配置ThreadPoolTaskScheduler,请执行以下操作:

@Bean
public ThreadPoolTaskScheduler threadPoolTaskScheduler(){
    ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
    scheduler.setThreadNamePrefix("ThreadPoolTaskScheduler");
    scheduler.setPoolSize(50);
    return scheduler ;
}
蓟辰沛
2023-03-14

所有已调度的标记调用都将使用默认的单线程执行器来调度任务(异步或其他)。

所有@Async任务都被移交给不同的aysnc线程池执行器,以便使用AOP拦截器执行。

我认为您的困惑来自异步方法立即返回的事实,但当deleteOldData运行时,它是同步运行的,因为只有一个线程,它会阻止任何其他计划任务的执行。

由于计划任务有默认的线程池(单线程),因此会一个接一个地计划这些任务。

其他带有@Async注释的方法即使完成或未完成也会执行。在某些情况下,我有两种同时执行的方法。但在执行deleteOldData时,异步方法会停止运行,直到完成为止。这是我不理解的,抱歉:/–

这与调度不同——这是您的异步执行程序发挥作用的地方,它们同时运行。

您可以通过以下两种方式之一修复此问题:

您可以在应用程序属性中使用spring.task.scheduling.pool.size=10来设置任务调度程序的池大小。

或者,使用不同的任务调度器。继续为计划的任务使用默认计划程序,并为异步任务配置如下内容(删除计划注释)

需要增强功能才能将任务调度器传递到计划的注释,直到您必须手动计划任务。

为异步调用注册一个新的任务调度器,并在构造后阶段调度方法。类似于

配置

@Bean("asyncTaskScheduler")
public TaskScheduler asyncTaskScheduler() {
  return new ThreadPoolTaskScheduler();
}

服务

@Autowired
private TaskScheduler asyncTaskScheduler;

@PostConstruct
void schedule() {
  asyncTaskScheduler.scheduleAtFixedRate(this::checkAvailableTasks, 20000L);
  asyncTaskScheduler.scheduleAtFixedDelay(this::checkBrokenEngines, 20000L);
}

@Async
public void checkBrokenEngines() {...}

@Async
public void checkAvailableTasks() throws Exception {...}
 类似资料:
  • 对于这个程序,实例变量的创建和模型需要是一个字符串,价格需要是我已经有的两倍,但不确定如何处理需要是int类型的年份,大于1900。然后我需要用参数做一个构造函数,我也做了,但是toString需要用setter和getters方法返回Car对象的字符串表示。所以我在试图为setter想出一些东西时遇到了问题,如果我做对了这一部分。 这部分是Cartest驱动程序,我不确定我这样做是否正确。我必须

  • Spring使用Quartz的顺序作业计划 我有三个或更多的工作,这取决于他们各自以前的工作,他们将按顺序运行。如果完成运行,当完成运行。如果在上一个中发生任何错误,则不会激发下一个触发的作业。我试图了解工作链使用石英,但无法通过它。 作业顺序如下所示 提前谢了。

  • 我想每天使用Spring Boot发送电子邮件,用户指定发送时间,我使用石英来安排我的工作。电子邮件的收件人有(id、emailAddress、截止日期)电子邮件将发送给截止日期=今天X...(用户指定X)。例如:用户指定X是1号,所以我们对明天有截止日期的人感兴趣。 第1天:应用程序向截止日期为今天1的人发送电子邮件。。第二天:我希望应用程序在第二天将电子邮件发送给新的收件人,但使用下面的代码,

  • 问题内容: 我需要在Flask应用程序上定期运行某些任务。我决定使用一个简单的库-Schedule(https://github.com/dbader/schedule)来执行此操作。我在与主应用程序线程不同的线程上运行任务计划程序。这是相关的代码片段。 运行此程序时,我想要“运行定期任务!” 每10秒打印一次。但是,这是我得到的输出。 显然,由于某种原因,任务似乎每10秒执行两次,而不是一次。但

  • 我对使用REST API的订阅功能有一些疑问。我们已经使用“快速结账NVP/SOAP集成”实现了定期支付,但对我们来说这不是最佳选择,因为: Webhooks比IPN消息更容易、更可用; 我们不能强迫顾客从PayPal余额中付款。 所以我想用REST API重写。我认为流程会是这样的: < li >用户按下按钮,我们第一次请求获取身份验证令牌; < li >创建计费计划; < li >启用计费计划

  • 我希望每天早上9点完成一项任务。我得到了一些有趣的结果。目前我的工作是这样的: 这是怎么回事?这是正确的表达吗?