Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Correct usage of @Async, @Scheduled and thread-pool in Spring Boot

I wrote the question for a long time and it is long. But I tried to show as much as possible what I did and what is not clear. Please finish reading and thank you for your patience!

I tried many experiments, write spring doc spring doc, (write questions in this site) But still not understand the full picture.

I have a task to implement some schedulers in one spring-boot server.

  1. First Scheduler will check data in DB every 1 second and run some logic.
  2. Second Scheduler will send requests to third-party service every 10 millisecond.

South schedulers must work with thread-pool and have different settings. For example first - 5 threads, second - 10 threads. While I understood, I tried several options and finally got confused, what should I choose and how to use it more correctly:

For test I create 2 beans with logics and will call methods from this beans every time:

@Slf4j
@Component
public class TestBean {

    public void test(){
        try {
            Thread.sleep(9000L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        log.info("First bean print");
    }
}

and

@Slf4j
@Component
public class TestBean2 {

    public void test(){
        try {
            Thread.sleep(9000L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        log.info("Second bean print");
    }
}

I still do not understand the difference, what and when to use - @Scheduled annotation or TaskScheduler from code. I tried to create a method with @Scheduled annotation:

@Slf4j
@Component
public class MyScheduler {

    private final TestBean testBean;
    private final TestBean2 testBean2;

    public MyScheduler(TestBean testBean, TestBean2 testBean2) {
        this.testBean = testBean;
        this.testBean2 = testBean2;
    }

    @Scheduled(fixedRate = 1000L)
    public void test() {
        testBean.test();//call method from first bean every 1 sec
    }
}

Output log:

2018-09-05 13:17:28.799  INFO 10144 --- [pool-1-thread-1] com.example.scheduling.TestBean          : First bean print
2018-09-05 13:17:37.799  INFO 10144 --- [pool-1-thread-1] com.example.scheduling.TestBean          : First bean print
2018-09-05 13:17:46.799  INFO 10144 --- [pool-1-thread-1] com.example.scheduling.TestBean          : First bean print

Work one thread and print log from first bean each 9 sec. After that I add TaskScheduler:

@Bean
ThreadPoolTaskScheduler taskScheduler(){
    ThreadPoolTaskScheduler threadPoolTaskScheduler = new ThreadPoolTaskScheduler();
    threadPoolTaskScheduler.setPoolSize(5);
    threadPoolTaskScheduler.setAwaitTerminationSeconds(60);
    threadPoolTaskScheduler.setThreadNamePrefix("TASK_SCHEDULER_FIRST-");
    return threadPoolTaskScheduler;
}

And start app. Output:

2018-09-05 13:21:40.973  INFO 7172 --- [HEDULER_FIRST-1] com.example.scheduling.TestBean          : First bean print
2018-09-05 13:21:49.973  INFO 7172 --- [HEDULER_FIRST-1] com.example.scheduling.TestBean          : First bean print
2018-09-05 13:21:58.973  INFO 7172 --- [HEDULER_FIRST-2] com.example.scheduling.TestBean          : First bean print
2018-09-05 13:22:07.973  INFO 7172 --- [HEDULER_FIRST-1] com.example.scheduling.TestBean          : First bean print

Each 9 sec but different threads print log from first bean. After that I try inject TaskScheduler and run schedule by another way:

@Slf4j
@Component
public class MyScheduler {

    private final TestBean testBean;
    private final TestBean2 testBean2;
    private final ThreadPoolTaskScheduler taskScheduler;

    public MyScheduler(TestBean testBean, TestBean2 testBean2, ThreadPoolTaskScheduler taskScheduler) {
        this.testBean = testBean;
        this.testBean2 = testBean2;
        this.taskScheduler = taskScheduler;
    }

    @PostConstruct
    public void test() {
        taskScheduler.scheduleAtFixedRate(testBean::test, 1000L);
        testBean.test();
    }
}

But got similar output:

2018-09-05 13:25:54.541  INFO 7044 --- [HEDULER_FIRST-1] com.example.scheduling.TestBean          : First bean print
2018-09-05 13:26:03.541  INFO 7044 --- [HEDULER_FIRST-2] com.example.scheduling.TestBean          : First bean print
2018-09-05 13:26:12.541  INFO 7044 --- [HEDULER_FIRST-1] com.example.scheduling.TestBean          : First bean print
2018-09-05 13:26:21.541  INFO 7044 --- [HEDULER_FIRST-3] com.example.scheduling.TestBean          : First bean print

After that I read that I need use @Async annotation and start bean's method in async:

@Slf4j
@Component
public class TestBean {

    @Async
    public void test(){
        try {
            Thread.sleep(9000L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        log.info("First bean print");
    }
}

Output:

2018-09-05 13:28:07.868  INFO 8608 --- [HEDULER_FIRST-2] com.example.scheduling.TestBean          : First bean print
2018-09-05 13:28:07.868  INFO 8608 --- [HEDULER_FIRST-3] com.example.scheduling.TestBean          : First bean print
2018-09-05 13:28:08.860  INFO 8608 --- [HEDULER_FIRST-4] com.example.scheduling.TestBean          : First bean print
2018-09-05 13:28:09.860  INFO 8608 --- [HEDULER_FIRST-1] com.example.scheduling.TestBean          : First bean print
2018-09-05 13:28:10.860  INFO 8608 --- [HEDULER_FIRST-5] com.example.scheduling.TestBean          : First bean print

Every 1 sec start new Thread. That's it! But what if I return @Scheduled annotation:

@Scheduled(fixedRate = 1000L)
public void test() {
    testBean.test();//async method
}

The result is the same as in the previous version. exactly what is needed!

But now I want use second bean. I Make method into second bean Async too and try start:

@Scheduled(fixedRate = 1000L)
public void test() {
    testBean.test();
    testBean2.test();
}

Output:

2018-09-05 13:32:46.079  INFO 11108 --- [HEDULER_FIRST-1] com.example.scheduling.TestBean2         : Second bean print
2018-09-05 13:32:46.079  INFO 11108 --- [HEDULER_FIRST-2] com.example.scheduling.TestBean          : First bean print
2018-09-05 13:32:47.074  INFO 11108 --- [HEDULER_FIRST-3] com.example.scheduling.TestBean          : First bean print
2018-09-05 13:32:47.074  INFO 11108 --- [HEDULER_FIRST-4] com.example.scheduling.TestBean2         : Second bean print
2018-09-05 13:32:48.074  INFO 11108 --- [HEDULER_FIRST-5] com.example.scheduling.TestBean          : First bean print

Both methods use ONE ThreadPoolTaskScheduler with 5 threads. But I need start each method with different ThreadPoolTaskScheduler. I create second ThreadPoolTaskScheduler:

@Bean
ThreadPoolTaskScheduler taskScheduler2(){
    ThreadPoolTaskScheduler threadPoolTaskScheduler = new ThreadPoolTaskScheduler();
    threadPoolTaskScheduler.setPoolSize(9);
    threadPoolTaskScheduler.setAwaitTerminationSeconds(60);
    threadPoolTaskScheduler.setThreadNamePrefix("TASK_SCHEDULER_SECOND-");
    return threadPoolTaskScheduler;
}

And start:

2018-09-05 13:35:31.152  INFO 14544 --- [           main] c.e.scheduling.SchedulingApplication     : Started SchedulingApplication in 1.669 seconds (JVM running for 2.141)
2018-09-05 13:35:40.134  INFO 14544 --- [cTaskExecutor-2] com.example.scheduling.TestBean2         : Second bean print
2018-09-05 13:35:40.134  INFO 14544 --- [cTaskExecutor-1] com.example.scheduling.TestBean          : First bean print
2018-09-05 13:35:41.127  INFO 14544 --- [cTaskExecutor-4] com.example.scheduling.TestBean2         : Second bean print
2018-09-05 13:35:41.127  INFO 14544 --- [cTaskExecutor-3] com.example.scheduling.TestBean          : First bean print
2018-09-05 13:35:42.127  INFO 14544 --- [cTaskExecutor-5] com.example.scheduling.TestBean          : First bean print
2018-09-05 13:35:42.127  INFO 14544 --- [cTaskExecutor-6] com.example.scheduling.TestBean2         : Second bean print

Both beans print log but with cTaskExecutor and not use tasckScheduler1 or tasckScheduler2

It is my first question - Why? How can it worked?

Now I tried use this implementation:

@Slf4j
@Component
public class MyScheduler {

    private final TestBean testBean;
    private final TestBean2 testBean2;
    private final ThreadPoolTaskScheduler poolTaskScheduler1;
    private final ThreadPoolTaskScheduler poolTaskScheduler2;

    public MyScheduler(TestBean testBean, TestBean2 testBean2,
                       @Qualifier("first") ThreadPoolTaskScheduler poolTaskScheduler1,
                       @Qualifier("second") ThreadPoolTaskScheduler poolTaskScheduler2) {
        this.testBean = testBean;
        this.testBean2 = testBean2;
        this.poolTaskScheduler1 = poolTaskScheduler1;
        this.poolTaskScheduler2 = poolTaskScheduler2;
    }

//    @Scheduled(fixedRate = 1000L)
    @PostConstruct
    public void test() {
        poolTaskScheduler1.scheduleAtFixedRate(testBean::test, 1000L);
        poolTaskScheduler2.scheduleAtFixedRate(testBean2::test, 1000L);
    }
}

Output: nothing changed.

And In finished I revert code:

@Scheduled(fixedRate = 1000L)
public void test() {
    testBean.test();
    testBean2.test();
}

And use @Async with qualifier:

@Async("first")
@Async("second")

Output:

2018-09-05 13:44:11.489  INFO 7432 --- [EDULER_SECOND-1] com.example.scheduling.TestBean2         : Second bean print
2018-09-05 13:44:11.489  INFO 7432 --- [HEDULER_FIRST-1] com.example.scheduling.TestBean          : First bean print
2018-09-05 13:44:12.484  INFO 7432 --- [EDULER_SECOND-2] com.example.scheduling.TestBean2         : Second bean print
2018-09-05 13:44:12.484  INFO 7432 --- [HEDULER_FIRST-2] com.example.scheduling.TestBean          : First bean print
2018-09-05 13:44:13.484  INFO 7432 --- [HEDULER_FIRST-3] com.example.scheduling.TestBean          : First bean print
2018-09-05 13:44:13.484  INFO 7432 --- [EDULER_SECOND-3] com.example.scheduling.TestBean2         : Second bean print
2018-09-05 13:44:14.484  INFO 7432 --- [EDULER_SECOND-4] com.example.scheduling.TestBean2         : Second bean print
2018-09-05 13:44:14.484  INFO 7432 --- [HEDULER_FIRST-4] com.example.scheduling.TestBean          : First bean print
2018-09-05 13:44:15.484  INFO 7432 --- [EDULER_SECOND-5] com.example.scheduling.TestBean2         : Second bean print
2018-09-05 13:44:15.484  INFO 7432 --- [HEDULER_FIRST-5] com.example.scheduling.TestBean          : First bean print
2018-09-05 13:44:16.483  INFO 7432 --- [EDULER_SECOND-6] com.example.scheduling.TestBean2         : Second bean print
2018-09-05 13:44:17.483  INFO 7432 --- [EDULER_SECOND-7] com.example.scheduling.TestBean2         : Second bean print
2018-09-05 13:44:18.483  INFO 7432 --- [EDULER_SECOND-8] com.example.scheduling.TestBean2         : Second bean print
2018-09-05 13:44:19.483  INFO 7432 --- [EDULER_SECOND-9] com.example.scheduling.TestBean2         : Second bean print

exactly what is needed! But I do not understand whether I'm doing the right thing

If I change ThreadPoolTaskScheduler to ThreadPoolTaskExecutor everything works the same way. So what should I use?

ThreadPoolTaskScheduler or ThreadPoolTaskExecutor @Scheduled or ThreadPoolTaskScheduler/ThreadPoolTaskExecutor from code? @Scheduled with ThreadPoolTaskScheduler/ThreadPoolTaskExecutor from code or @Async?

like image 802
ip696 Avatar asked Sep 05 '18 10:09

ip696


People also ask

What is the use of @async in Spring boot?

The @EnableAsync annotation switches on Spring's ability to run @Async methods in a background thread pool. This class also customizes the Executor by defining a new bean. Here, the method is named taskExecutor , since this is the specific method name for which Spring searches.

What is the use of @async annotation?

Simply put, annotating a method of a bean with @Async will make it execute in a separate thread. In other words, the caller will not wait for the completion of the called method. One interesting aspect in Spring is that the event support in the framework also has support for async processing if necessary.

What is @scheduled in Spring boot?

The @EnableScheduling annotation is used to enable the scheduler for your application. This annotation should be added into the main Spring Boot application class file. @SpringBootApplication @EnableScheduling public class DemoApplication { public static void main(String[] args) { SpringApplication.

What is the use of TaskExecutor in Spring?

The TaskExecutor was originally created to give other Spring components an abstraction for thread pooling where needed. Components such as the ApplicationEventMulticaster , JMS's AbstractMessageListenerContainer , and Quartz integration all use the TaskExecutor abstraction to pool threads.


Video Answer


1 Answers

Let's go through your questions one by one:

  1. You had your custom ThreadPoolTaskSchedulers (TaskExecutor Beans) but you didn't configure @Async properly. By default Spring uses SimpleAsyncTaskExecutor to execute your task. That's why you only saw log with abbreviated thread name [cTaskExecutor-1], [cTaskExecutor-2], etc.. If you want to make use of taskScheduler1 or taskScheduler2 you have to configure @Async with corresponding name.

    @Async("taskScheduler1")
    public void test(){
        try {
            Thread.sleep(9000L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        log.info("First bean print");
    }
  2. Later on you configured executor names "first" and "second" for your beans so your async tasks worked. @Async looked up and found the executor Beans with the provided names.

  3. TaskScheduler is designed for scheduling tasks, and TaskExecutor is designed for async tasks. ThreadPoolTaskScheduler implements both TaskScheduler and TaskExecutor. ThreadPoolTaskExecutor implements TaskExecutor and not TaskScheduler.

    You was using a scheduling task for firing async tasks so ThreadPoolTaskScheduler and ThreadPoolTaskExecutor are interchangeable unless you wanna use ThreadPoolTaskExecutor's fine-grained configuration over the thread pool such as setCorePoolSize, setMaxPoolSize,.. If you use more than 1 scheduling task you want to implement a ThreadPoolTaskScheduler because by default all @Scheduled tasks are executed in a default thread pool of size 1 created by Spring.

like image 128
Quang Vien Avatar answered Oct 12 '22 10:10

Quang Vien