2

I have a spring application which has two classes annotated with @Component, in each class, I have a method annotated with @Scheduled, that means I want to run those methods at the fixed interval like this :

This is First Component which has a readFirstComponent() method, this methos read something from somewhere and it takes awhile to carries out, @Component public class FirstComp {

@Scheduled(fixedRate = 20000 )
public void readFirstComponent() {
    // body
}

//other methods }

Second component almost doing the same as First Component does,

@Component

public class SecondComp {

@Scheduled(fixedRate = 20000 )
public void readSecondComponent() {
    // body
}

//other methods }

I have a runner class to start the application

@SpringBootApplication
@EnableScheduling
@ImportResource("classpath:spring/Spring-AutoScan.xml")
public class Application {
public static void main(final String args[]) {
    SpringApplication.run(Application.class);    
}

}

When I start the application FirtComp is starting and readFirstComponent() carrying out after nearly 14s coming to the end THEN readSecondComponent() from SecondComp is starting, and so on, My problem is that I want to start both methods concurrently, please help me to fix this problem

Mehdi Alisoltani
  • 349
  • 3
  • 13

2 Answers2

5

By default there is only one thread to run scheduling tasks.

You could read about it here and find out how to configure the scheduler to get a pool with more threads.

27.4.1 Enable scheduling annotations

To enable support for @Scheduled and @Async annotations add @EnableScheduling and @EnableAsync to one of your @Configuration classes:

@Configuration
@EnableAsync
@EnableScheduling
public class AppConfig {
}

You are free to pick and choose the relevant annotations for your application. For example, if you only need support for @Scheduled, simply omit @EnableAsync. For more fine-grained control you can additionally implement the SchedulingConfigurer and/or AsyncConfigurer interfaces. See the javadocs for full details.

If you prefer XML configuration use the element.

<task:annotation-driven executor="myExecutor" scheduler="myScheduler"/>
<task:executor id="myExecutor" pool-size="5"/>
<task:scheduler id="myScheduler" pool-size="10"/>

Notice with the above XML that an executor reference is provided for handling those tasks that correspond to methods with the @Async annotation, and the scheduler reference is provided for managing those methods annotated with @Scheduled.

Since you use annotations to configure you beans it would be better to implement the SchedulingConfigurer.

Like this:

@Configuration
@EnableScheduling
public class SchedulingConfig implements SchedulingConfigurer {

@Override
public void configureTasks(
  ScheduledTaskRegistrar taskRegistrar) {
    taskRegistrar.setScheduler(taskExecutor());
}

@Bean(destroyMethod = "shutdown")
public Executor taskExecutor() {
    return Executors.newScheduledThreadPool(10);
}
}
evkm
  • 311
  • 3
  • 6
4

Old question but I was dealing with this myself. I could not use the provided solution above, because I needed to ensure only one instance of each method was running at a time. And increasing default schedule threadPool size meant I could end up in trouble if methods took longer than shcedule interval.

So instead I created 2 thread pools of a single thread each, and then annotate each method to use the relevant thread pool (i.e. single thread).

Creating the thread pools:

@SpringBootApplication
@EnableScheduling
@EnableAsync(proxyTargetClass=true)
public class MainApplication {

    public static void main(String[] args) {
        SpringApplication application = new SpringApplication(MainApplication.class);   
        application.run(args);
    }       

    @Bean("schedulePool1")
    public Executor jobPool() {
        ThreadPoolTaskExecutor exec = new ThreadPoolTaskExecutor();
        exec.setCorePoolSize(1);
        exec.setMaxPoolSize(1);
        exec.setQueueCapacity(10);
        exec.setThreadNamePrefix("first-");
        exec.initialize();
        return exec;
    }       

    @Bean("schedulePool2")
    public Executor jobPool2() {
        ThreadPoolTaskExecutor exec = new ThreadPoolTaskExecutor();
        exec.setCorePoolSize(1);
        exec.setMaxPoolSize(1);
        exec.setQueueCapacity(10);
        exec.setThreadNamePrefix("second-");
        exec.initialize();
        return exec;
    }
}

Then you can add the @Async annotation to your two sheduled methods.

@Async("schedulePool1")
@Scheduled(fixedRate = 20000 )
public void readFirstComponent() {
    // body
}

And

@Async("schedulePool2")
@Scheduled(fixedRate = 20000 )
public void readSecondComponent() {
    // body
}

Then in your logs you should see stuff with correct [thread]:

2020-02-21 20:47:01 [first-1] INFO  sodved.JobSchedule - running readFirstComponent
...
2020-02-21 20:47:01 [second-1] INFO  sodved.JobService - running readSecondComponent
Sodved
  • 8,428
  • 2
  • 31
  • 43
  • Exactly what I was looking for. Thank you for sharing! – Volodymyr Krupach Mar 19 '21 at 14:49
  • what if you don't used `Async` annotation and just set the pool size to number of components? In case of two components => `spring.task.scheduling.pool.size=2`. You also need to use `fixDelay` instead of `fixRate` to have interval period just after previous completion, so no issue if a method takes longer than interval – Soheil Pourbafrani Jun 08 '22 at 15:16
  • @SoheilPourbafrani then the same method can run twice at the same time – flup Aug 10 '22 at 17:01
  • @flup Never! Suppose there are two threads and two different methods. Having the same fixDelay in addition to the blocking method calls (Synch) guarantees that. – Soheil Pourbafrani Aug 10 '22 at 21:53
  • Ah. Yes. I overlooked the fixedDelay part. – flup Aug 11 '22 at 22:44