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.
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
?
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.
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.
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.
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.
Let's go through your questions one by one:
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");
}
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With