In the past, application processing was often sufficient in a synchronous way of executing and receiving results. However, in recent years, with the progress of mobile devices such as smartphones and multi-core processors, Asynchronous processing that can perform multiple processing at the same time is also required.
In Spring, you can easily perform asynchronous processing on another thread by using the @ Async
annotation.
Let's check how to create asynchronous processing using @ Async
.
When you execute any instruction in Java, the processing is done in a thread. In the case of single thread, the instructions are processed sequentially on the thread. Therefore, simultaneous processing is not possible.
Therefore, prepare multiple threads and execute instructions on each thread. Asynchronous parallel processing is realized by executing instructions in another thread without waiting for the end.
Spring Dependencies
Set to enable asynchronous processing in Spring Boot application.
All you have to do is add the @EnableAsync
annotation to the configuration class (@Configuration
annotation or the class with the @ SpringBootApplication
annotation).
@SpringBootApplication
@EnableAsync
class SimpleApplication
Annotate the function that you want to perform asynchronous processing in another thread with @Async
.
The function with this @ Async
annotation is treated as the function to be processed asynchronously, and the processed instruction is executed in another thread.
import org.slf4j.LoggerFactory
import org.springframework.scheduling.annotation.Async
import org.springframework.stereotype.Service
import java.util.concurrent.TimeUnit
@Service("Async Task Service")
class AsyncTaskService {
val logger = LoggerFactory.getLogger(this::class.java.name)
@Async
fun standardTask() {
logger.info("Task Start")
TimeUnit.SECONDS.sleep(5)
logger.info("Task End")
}
}
Place a REST controller that calls the class responsible for asynchronous processing with the @ Async
annotation.
Set the access endpoint as / async
.
This allows this asynchronous process to be called at http: // localhost: 8080 / async
.
@RestController
@RequestMapping("/async")
class AsyncTaskController {
@GetMapping
fun callStandardTask() = service.standardTask()
}
Let's actually call asynchronous processing 5 times in a row.
$ curl http://localhost:8080/async
$ curl http://localhost:8080/async
$ curl http://localhost:8080/async
$ curl http://localhost:8080/async
$ curl http://localhost:8080/async
The run-time log of the start and end of each asynchronous process is displayed on the standard output as shown below.
2018-12-19 20:50:22.662 INFO 22121 --- [cTaskExecutor-1] i.p.s.simple.service.AsyncTaskService : Normal Task Start
2018-12-19 20:51:00.545 INFO 22121 --- [cTaskExecutor-2] i.p.s.simple.service.AsyncTaskService : Normal Task Start
2018-12-19 20:51:01.886 INFO 22121 --- [cTaskExecutor-3] i.p.s.simple.service.AsyncTaskService : Normal Task Start
2018-12-19 20:51:04.397 INFO 22121 --- [cTaskExecutor-4] i.p.s.simple.service.AsyncTaskService : Normal Task Start
2018-12-19 20:51:05.550 INFO 22121 --- [cTaskExecutor-5] i.p.s.simple.service.AsyncTaskService : Normal Task Start
2018-12-19 20:51:06.887 INFO 22121 --- [cTaskExecutor-1] i.p.s.simple.service.AsyncTaskService : Normal Task End
2018-12-19 20:51:09.400 INFO 22121 --- [cTaskExecutor-2] i.p.s.simple.service.AsyncTaskService : Normal Task End
2018-12-19 20:52:03.338 INFO 22121 --- [cTaskExecutor-3] i.p.s.simple.service.AsyncTaskService : Normal Task End
2018-12-19 20:52:05.123 INFO 22121 --- [cTaskExecutor-4] i.p.s.simple.service.AsyncTaskService : Normal Task End
2018-12-19 20:52:06.290 INFO 22121 --- [cTaskExecutor-5] i.p.s.simple.service.AsyncTaskService : Normal Task End
Looking at this result, you can see that 5 threads from cTaskExecutor-1
to cTaskExecutor-5
are created and processed, and each is processed asynchronously.
In the asynchronous process executed earlier, threads were created for the number of calls. What if this is an application that is called very often? It is possible that a large number of threads will be created, exhausting system resources, reducing performance, and in the worst case, system down. There are many Java EE application servers that provide the ability to control the number of threads as a platform. So what do you do with a Spring Boot application that doesn't use a Java EE application server like this one?
When performing asynchronous processing in Spring, by adding @ Async
annotation, SimpleAsyncTaskExecutor
is used by default to create another thread and asynchronous processing is performed.
On the other hand, if you want to configure the thread pool to control the number of threads, configure and use ThreadPoolTaskExecutor
.
Define it in the configuration class with @EnableAsync
.
Below, we are creating two types of pools.
@SpringBootApplication
@EnableAsync
class SimpleApplication {
@Bean
fun normalTaskExecutor(): TaskExecutor = ThreadPoolTaskExecutor().apply {
corePoolSize = 1
setQueueCapacity(5)
maxPoolSize = 1
setThreadNamePrefix("NormalThread-")
setWaitForTasksToCompleteOnShutdown(true)
}
@Bean
fun prioritizedTaskExecutor(): TaskExecutor = ThreadPoolTaskExecutor().apply {
corePoolSize = 5
setQueueCapacity(5)
maxPoolSize = 5
setThreadNamePrefix("PrioritizedThread-")
setWaitForTasksToCompleteOnShutdown(true)
}
}
Set the following attributes to configure the thread pool.
name | Contents |
---|---|
corePoolSize | Create the number of threads up to this setting value |
setQueueCapacity | When the number of corePoolSize is exceeded, queuing up to this setting value |
maxPoolSize | When queuing to the maximum of setQueueCapacity, the number of threads is created up to this set value. |
Explicitly specify the bean name of ThreadPoolTaskExecutor
with the @Async
annotation as shown below.
Determines which ThreadPoolTaskExecutor will be asynchronous.
@Async("prioritizedTaskExecutor")
fun prioritizedTask() {
logger.info("Prioritized Task Start")
TimeUnit.SECONDS.sleep(5)
logger.info("Prioritized Task End")
}
@Async("normalTaskExecutor")
fun normalTask() {
logger.info("Normal Task Start")
TimeUnit.SECONDS.sleep(5)
logger.info("Normal Task End")
}
Let's look at the result of asynchronous processing with the number of thread pools set to 1
.
$ curl http://localhost:8080/async/normal
$ curl http://localhost:8080/async/normal
$ curl http://localhost:8080/async/normal
$ curl http://localhost:8080/async/normal
$ curl http://localhost:8080/async/normal
As shown below, you can see that the processing is performed only by the NormalThread-1
thread.
As specified, it is processed with the number of threads 1
.
2018-12-19 22:30:17.654 INFO 22746 --- [ NormalThread-1] i.p.s.simple.service.AsyncTaskService : Normal Task Start
2018-12-19 22:30:22.658 INFO 22746 --- [ NormalThread-1] i.p.s.simple.service.AsyncTaskService : Normal Task End
2018-12-19 22:30:24.437 INFO 22746 --- [ NormalThread-1] i.p.s.simple.service.AsyncTaskService : Normal Task Start
2018-12-19 22:30:29.441 INFO 22746 --- [ NormalThread-1] i.p.s.simple.service.AsyncTaskService : Normal Task End
2018-12-19 22:30:29.441 INFO 22746 --- [ NormalThread-1] i.p.s.simple.service.AsyncTaskService : Normal Task Start
2018-12-19 22:30:34.442 INFO 22746 --- [ NormalThread-1] i.p.s.simple.service.AsyncTaskService : Normal Task End
2018-12-19 22:30:34.443 INFO 22746 --- [ NormalThread-1] i.p.s.simple.service.AsyncTaskService : Normal Task Start
2018-12-19 22:30:39.445 INFO 22746 --- [ NormalThread-1] i.p.s.simple.service.AsyncTaskService : Normal Task End
2018-12-19 22:30:39.446 INFO 22746 --- [ NormalThread-1] i.p.s.simple.service.AsyncTaskService : Normal Task Start
2018-12-19 22:30:44.455 INFO 22746 --- [ NormalThread-1] i.p.s.simple.service.AsyncTaskService : Normal Task End
Next, let's look at asynchronous processing that sets the number of thread pools to 5
.
$ curl http://localhost:8080/async/high
$ curl http://localhost:8080/async/high
$ curl http://localhost:8080/async/high
$ curl http://localhost:8080/async/high
$ curl http://localhost:8080/async/high
Here you can see that 5 threads from HighThread-1
to HighThread-5
have been created and used.
As you can see, you can easily perform asynchronous processing considering the control of the thread pool.
2018-12-19 22:37:55.784 INFO 22746 --- [HighThread-1] i.p.s.simple.service.AsyncTaskService : Prioritized Task Start
2018-12-19 22:37:57.469 INFO 22746 --- [HighThread-2] i.p.s.simple.service.AsyncTaskService : Prioritized Task Start
2018-12-19 22:37:57.898 INFO 22746 --- [HighThread-3] i.p.s.simple.service.AsyncTaskService : Prioritized Task Start
2018-12-19 22:37:58.956 INFO 22746 --- [HighThread-4] i.p.s.simple.service.AsyncTaskService : Prioritized Task Start
2018-12-19 22:37:59.582 INFO 22746 --- [HighThread-5] i.p.s.simple.service.AsyncTaskService : Prioritized Task Start
2018-12-19 22:38:00.787 INFO 22746 --- [HighThread-1] i.p.s.simple.service.AsyncTaskService : Prioritized Task End
2018-12-19 22:38:02.473 INFO 22746 --- [HighThread-2] i.p.s.simple.service.AsyncTaskService : Prioritized Task End
2018-12-19 22:38:02.900 INFO 22746 --- [HighThread-3] i.p.s.simple.service.AsyncTaskService : Prioritized Task End
2018-12-19 22:38:03.957 INFO 22746 --- [HighThread-4] i.p.s.simple.service.AsyncTaskService : Prioritized Task End
2018-12-19 22:38:04.586 INFO 22746 --- [HighThread-5] i.p.s.simple.service.AsyncTaskService : Prioritized Task End
Asynchronous processing is a processing that can be effectively used not only in Web applications but also in batch applications. Since asynchronous processing can be performed while easily creating threads and controlling the thread pool in this way, it seems that there are many possible situations in which it can be used.
Recommended Posts