1, Asynchronous call
@EnableAsync enables asynchronous calls
Start class addition@EnableAsync annotation
@SpringBootApplication @EnableAsync public class DemoApplication { public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); } }
Create asynchronous / synchronous method
@RestController public class TestController { private Logger logger = LoggerFactory.getLogger(this.getClass()); @Autowired private TestService testService; @GetMapping("async") public void testAsync() { long start = System.currentTimeMillis(); logger.info("Call asynchronous method start..."); for (int i = 0; i < 2; i++) { testService.asyncMethod(); } logger.info("End of call to asynchronous method..."); long end = System.currentTimeMillis(); logger.info("Total time:{} ms", end - start); } @GetMapping("sync") public void testSync() { long start = System.currentTimeMillis(); logger.info("Call synchronization method start..."); for (int i = 0; i < 2; i++) { testService.syncMethod(); } logger.info("End of calling synchronization method..."); long end = System.currentTimeMillis(); logger.info("Total time:{} ms", end - start); } }
@Async identifies asynchronous methods
@Async The annotation identification method is asynchronous
@Service public class TestService { private Logger logger = LoggerFactory.getLogger(this.getClass()); @Async public void asyncMethod() { logger.info("Asynchronous method execution started..."); sleep(); logger.info("Thread Name:{}", Thread.currentThread().getName()); logger.info("End of asynchronous method execution..."); } public void syncMethod() { logger.info("Synchronization method execution started..."); sleep(); logger.info("Thread Name:{}", Thread.currentThread().getName()); logger.info("End of synchronization method execution..."); } private void sleep() { try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } } }
Asynchronous method testing
INFO 11340 --- [nio-8080-exec-1] cn.ybzy.demo.controller.TestController : Call asynchronous method start... INFO 11340 --- [nio-8080-exec-1] cn.ybzy.demo.controller.TestController : End of call to asynchronous method... INFO 11340 --- [nio-8080-exec-1] cn.ybzy.demo.controller.TestController : Total time: 5 ms INFO 11340 --- [ task-2] cn.ybzy.demo.service.TestService : Asynchronous method execution started... INFO 11340 --- [ task-1] cn.ybzy.demo.service.TestService : Asynchronous method execution started... INFO 11340 --- [ task-2] cn.ybzy.demo.service.TestService : Thread Name: task-2 INFO 11340 --- [ task-1] cn.ybzy.demo.service.TestService : Thread Name: task-1 INFO 11340 --- [ task-2] cn.ybzy.demo.service.TestService : End of asynchronous method execution... INFO 11340 --- [ task-1] cn.ybzy.demo.service.TestService : End of asynchronous method execution...
Synchronization method test
INFO 14904 --- [nio-8080-exec-1] cn.ybzy.demo.controller.TestController : Call synchronization method start... INFO 14904 --- [nio-8080-exec-1] cn.ybzy.demo.service.TestService : Synchronization method execution started... INFO 14904 --- [nio-8080-exec-1] cn.ybzy.demo.service.TestService : Thread Name: http-nio-8080-exec-1 INFO 14904 --- [nio-8080-exec-1] cn.ybzy.demo.service.TestService : End of synchronization method execution... INFO 14904 --- [nio-8080-exec-1] cn.ybzy.demo.service.TestService : Synchronization method execution started... INFO 14904 --- [nio-8080-exec-1] cn.ybzy.demo.service.TestService : Thread Name: http-nio-8080-exec-1 INFO 14904 --- [nio-8080-exec-1] cn.ybzy.demo.service.TestService : End of synchronization method execution... INFO 14904 --- [nio-8080-exec-1] cn.ybzy.demo.controller.TestController : End of calling synchronization method... INFO 14904 --- [nio-8080-exec-1] cn.ybzy.demo.controller.TestController : Total time: 2023 ms
2, Use of thread pool
spring encapsulates the multithreading implementation of Java, which is expressed as the TaskExecutor interface
public interface TaskExecutor extends Executor { void execute(Runnable task); }
Two important implementations:
ThreadPoolTaskExecutor
The ThreadPoolTaskExecutor internally wraps the ThreadPoolExecutor and provides various parameters that can configure the thread pool in the form of IOC
ThreadPoolTaskScheduler
The ThreadPoolTaskScheduler internally wraps the ScheduledThreadPoolExecutor. In addition to executing asynchronous tasks, it also supports the execution of timed / delayed tasks, which is an advanced feature.
Use default thread pool configuration
By default, SpringBoot has automatically configured the ThreadPoolTaskExecutor into the IOC container, which can be injected directly when needed
@SpringBootTest class SpringbootApplicationTests { @Autowired private ThreadPoolTaskExecutor threadPoolTaskExecutor; @Test public void test() { //Submit 10 tasks to the thread pool for (int i = 0; i < 10; i++) { int finalI = i; threadPoolTaskExecutor.execute(new Runnable() { @Override public void run() { System.out.println("execute task:" + finalI + " ThreadName: " + Thread.currentThread().getName()); } }); } System.out.println("Number of core threads:" + threadPoolTaskExecutor.getCorePoolSize()); System.out.println("Maximum number of threads:" + threadPoolTaskExecutor.getMaxPoolSize()); System.out.println("Thread wait timeout:" + threadPoolTaskExecutor.getKeepAliveSeconds()); System.out.println("Number of currently active threads:" + threadPoolTaskExecutor.getActiveCount()); System.out.println("The name prefix of the thread in the thread pool:" + threadPoolTaskExecutor.getThreadNamePrefix()); } }
execute task:1 ThreadName: task-2 execute task:2 ThreadName: task-3 execute task:3 ThreadName: task-4 execute task:0 ThreadName: task-1 execute task:4 ThreadName: task-5 execute task:5 ThreadName: task-6 execute task:6 ThreadName: task-7 execute task:7 ThreadName: task-8 execute task:8 ThreadName: task-7 execute task:9 ThreadName: task-6 Number of core threads:8 Maximum number of threads:2147483647 Thread wait timeout:60 Number of currently active threads:0 The name prefix of the thread in the thread pool:task-
Configure thread pool parameters
ThreadPoolTaskExecutor supports the reconfiguration of thread pool core parameters, which overrides the spring boot default configuration thread pool parameters
@Configuration public class AsyncPoolConfig { @Bean public ThreadPoolTaskExecutor asyncThreadPoolTaskExecutor() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); // The number of core threads in the thread pool. The default value is 1 (the reason why it cannot be reused) executor.setCorePoolSize(16); // The thread pool maintains the maximum number of threads. Only when the core threads are used up and the buffer queue is full, will it start to apply for threads that exceed the number of core threads. The default value is Integer.MAX_VALUE executor.setMaxPoolSize(60); // Buffer queue executor.setQueueCapacity(30); // The maximum lifetime of threads beyond the number of core threads when idle. The default is 60 seconds executor.setKeepAliveSeconds(60); // Sets the name prefix of threads in the thread pool executor.setThreadNamePrefix("asyncThread"); // Whether to wait for all threads to execute before closing the thread pool. The default value is false executor.setWaitForTasksToCompleteOnShutdown(true); // The waiting time of waitfortastocompleteonshutdown. The default value is 0, that is, do not wait executor.setAwaitTerminationSeconds(60); // The processing policy (reject task) when no thread can be used. The default policy is abortPolicy /** * callerRunsPolicy: The handler for the rejected task directly runs the rejected task in the calling thread of the execute method; If the executor is closed, the task is discarded * abortPolicy: Throw java.util.concurrent.RejectedExecutionException directly * discardOldestPolicy: When the number of threads in the thread pool is equal to the maximum number of threads, discard the last task to be executed in the thread pool and execute the newly transferred task * discardPolicy: When the number of threads in the thread pool is equal to the maximum number of threads, no action is taken */ ThreadPoolExecutor.CallerRunsPolicy callerRunsPolicy = new ThreadPoolExecutor.CallerRunsPolicy(); executor.setRejectedExecutionHandler(callerRunsPolicy); //Direct initialization executor.initialize(); return executor; } }
execute task:0 ThreadName: asyncThread1 execute task:1 ThreadName: asyncThread2 execute task:2 ThreadName: asyncThread3 execute task:5 ThreadName: asyncThread6 execute task:6 ThreadName: asyncThread7 execute task:3 ThreadName: asyncThread4 execute task:7 ThreadName: asyncThread8 Number of core threads:16 execute task:8 ThreadName: asyncThread9 execute task:9 ThreadName: asyncThread10 execute task:4 ThreadName: asyncThread5 Maximum number of threads:60 Thread wait timeout:60 Number of currently active threads:0 The name prefix of the thread in the thread pool:asyncThread
Optimization of asynchronous calls
In the asynchronous method of the above asynchronous call, a new thread will be started to execute, and the thread name is task - 1. By default, the asynchronous thread pool configuration prevents threads from being reused. Each time an asynchronous method is called, a new thread will be created. You can customize the asynchronous thread pool and configure the thread pool parameters to optimize the solution
@Specifies the thread pool Bean name on the Async annotation
@Async("asyncThreadPoolTaskExecutor") public void asyncMethod() { logger.info("Asynchronous method execution started..."); sleep(); logger.info("Thread Name:{}", Thread.currentThread().getName()); logger.info("End of asynchronous method execution..."); }
INFO 11340 --- [nio-8080-exec-1] cn.ybzy.demo.controller.TestController : Call asynchronous method start... INFO 11340 --- [nio-8080-exec-1] cn.ybzy.demo.controller.TestController : End of call to asynchronous method... INFO 11340 --- [nio-8080-exec-1] cn.ybzy.demo.controller.TestController : Total time: 5 ms INFO 11340 --- [ task-2] cn.ybzy.demo.service.TestService : Asynchronous method execution started... INFO 11340 --- [ task-1] cn.ybzy.demo.service.TestService : Asynchronous method execution started... INFO 11340 --- [ task-2] cn.ybzy.demo.service.TestService : Thread Name: asyncThread1 INFO 11340 --- [ task-1] cn.ybzy.demo.service.TestService : Thread Name: asyncThread2 INFO 11340 --- [ task-2] cn.ybzy.demo.service.TestService : End of asynchronous method execution... INFO 11340 --- [ task-1] cn.ybzy.demo.service.TestService : End of asynchronous method execution...
Time consuming statistics of multithreaded task execution
@Test public void test2() throws Exception { long start = System.currentTimeMillis(); int number=10; // Thread counter CountDownLatch countDownLatch = new CountDownLatch(number); // Add 10 tasks to be executed by the thread pool for (int i = 0; i < number; i++) { int finalI = i; threadPoolTaskExecutor.execute(new Runnable() { @Override public void run() { try { Thread.sleep(finalI*1000); } catch (InterruptedException e) { e.printStackTrace(); } // Thread counter self decrement countDownLatch.countDown(); System.out.println(Thread.currentThread().getName()+" time consuming: "+ (System.currentTimeMillis()-start)); } }); } //Block the current thread and wait for the thread pool thread to return latch=0 try { countDownLatch.await(1,TimeUnit.MINUTES); long end = System.currentTimeMillis(); System.out.println("Total task execution time: "+ (end-start)); } catch (InterruptedException e) { e.printStackTrace(); System.out.println("Task execution timeout..."); } }
threadPoolInfo-1 time consuming: 2 threadPoolInfo-2 time consuming: 1004 threadPoolInfo-3 time consuming: 2010 threadPoolInfo-4 time consuming: 3010 threadPoolInfo-5 time consuming: 4010 threadPoolInfo-6 time consuming: 5009 threadPoolInfo-7 time consuming: 6017 threadPoolInfo-8 time consuming: 7019 threadPoolInfo-9 time consuming: 8014 threadPoolInfo-10 time consuming: 9007 Total task execution time: 9007
Use of thread pool timed tasks
schedule() Press cron Please pay attention to the execution of the expression cron There is no guarantee that it can be executed at startup scheduleAtFixedRate() According to the fixed frequency, it can be implemented at startup scheduleWithFixedDelay() If the task time exceeds the fixed frequency, it shall be postponed according to the actual time of the task
@SpringBootTest class SpringbootApplicationTests { @Autowired private ThreadPoolTaskScheduler threadPoolTaskScheduler; @Test public void testCron() { String cron = "*/5 * * * * ?"; threadPoolTaskScheduler.schedule(new Runnable() { @Override public void run() { System.out.println(System.currentTimeMillis() + " schedule...... "); } }, new CronTrigger(cron)); while (true) { } } @Test public void testFixedSchedule() { ScheduledFuture<?> future = threadPoolTaskScheduler.scheduleAtFixedRate(new Runnable() { @Override public void run() { System.out.println(System.currentTimeMillis() + " scheduleAtFixedRate...."); } }, 1000); // Interrupt the thread and cancel the task // future.cancel(true); while (true) { } } }
3, Asynchronous callback
Asynchronous call callback
If the asynchronous method has a return value, you need to use Future to receive the callback value.
@Async("asyncThreadPoolTaskExecutor") public Future<Boolean> asyncMethod() { logger.info("Asynchronous method execution started..."); sleep(); logger.info("Thread Name:{}", Thread.currentThread().getName()); logger.info("End of asynchronous method execution..."); AsyncResult<Boolean> asyncResult = new AsyncResult<>(Boolean.TRUE); return asyncResult; }
@GetMapping("async") public void testAsync() throws Exception { long start = System.currentTimeMillis(); logger.info("Call asynchronous method start..."); for (int i = 0; i < 2; i++) { Future<Boolean> future = testService.asyncMethod(); //The get method of Future is a blocking method if (future.get()){ logger.info("The asynchronous method was called successfully..."); }else{ logger.info("Call to asynchronous method failed..."); } // If there is no return value within the set time, an exception is thrown // Boolean aBoolean = future.get(2, TimeUnit.SECONDS); } logger.info("End of call to asynchronous method..."); long end = System.currentTimeMillis(); logger.info("Total time:{} ms", end - start); }
The get method of Future is a blocking method
INFO 9868 --- [nio-8080-exec-1] cn.ybzy.demo.controller.TestController : Call asynchronous method start... INFO 9868 --- [ asyncThread1] cn.ybzy.demo.service.TestService : Asynchronous method execution started... INFO 9868 --- [ asyncThread1] cn.ybzy.demo.service.TestService : Thread Name: asyncThread1 INFO 9868 --- [ asyncThread1] cn.ybzy.demo.service.TestService : End of asynchronous method execution... INFO 9868 --- [nio-8080-exec-1] cn.ybzy.demo.controller.TestController : The asynchronous method was called successfully... INFO 9868 --- [ asyncThread2] cn.ybzy.demo.service.TestService : Asynchronous method execution started... INFO 9868 --- [ asyncThread2] cn.ybzy.demo.service.TestService : Thread Name: asyncThread2 INFO 9868 --- [ asyncThread2] cn.ybzy.demo.service.TestService : End of asynchronous method execution... INFO 9868 --- [nio-8080-exec-1] cn.ybzy.demo.controller.TestController : The asynchronous method was called successfully... INFO 9868 --- [nio-8080-exec-1] cn.ybzy.demo.controller.TestController : End of call to asynchronous method... INFO 9868 --- [nio-8080-exec-1] cn.ybzy.demo.controller.TestController : Total time: 2025 ms
Multithreaded task execution callback
public Boolean addTask() throws Exception { Future<Boolean> future = threadPoolTaskExecutor.submit(new Callable<Boolean>() { @Override public Boolean call() { // TODO business operation return Boolean.TRUE; } }); //Blocking 5s to get results return future.get(5, TimeUnit.SECONDS); } @Test public void test2() throws Exception { // Add 10 tasks to be executed by the thread pool for (int i = 0; i < 10; i++) { System.out.println("results of enforcement: " + this.addTask()); } }
4, Thread pool monitoring
Extend ThreadPoolTaskExecutor
The ThreadPoolTaskExecutor is extended to monitor the running status of the thread pool, such as automatically printing the running parameters of the thread pool when a task is submitted to the thread pool for execution
@Slf4j public class ThreadPoolInfo extends ThreadPoolTaskExecutor { /** * Thread pool health data information * * @param info */ private void threadPoolLogs(String info) { //Thread name prefix String prefix = this.getThreadNamePrefix(); //Total tasks long taskCount = this.getThreadPoolExecutor().getTaskCount(); //Number of tasks completed long completedTaskCount = this.getThreadPoolExecutor().getCompletedTaskCount(); //The number of threads currently executing the task int activeCount = this.getThreadPoolExecutor().getActiveCount(); //Number of tasks in the task waiting queue int queueSize = this.getThreadPoolExecutor().getQueue().size(); log.info("{},{},taskCout={},completedTaskCount={},activeCount={},queueSize={}", prefix, info, taskCount, completedTaskCount, activeCount, queueSize); } @Override public void execute(Runnable task) { super.execute(task); threadPoolLogs("execute "); } @Override public <T> Future<T> submit(Callable<T> task) { Future<T> future = super.submit(task); threadPoolLogs("submit"); return future; } }
Extended thread pool configuration
Add threadPoolInfo to the container in the configuration class
@Configuration public class AsyncPoolConfig { @Bean public ThreadPoolTaskExecutor threadPoolInfo() { ThreadPoolTaskExecutor threadPoolInfo = new ThreadPoolInfo(); threadPoolInfo.setCorePoolSize(16); threadPoolInfo.setMaxPoolSize(60); threadPoolInfo.setKeepAliveSeconds(60); threadPoolInfo.setQueueCapacity(30); threadPoolInfo.setThreadNamePrefix("threadPoolInfo-"); threadPoolInfo.setWaitForTasksToCompleteOnShutdown(true); threadPoolInfo.setAwaitTerminationSeconds(60); ThreadPoolExecutor.CallerRunsPolicy callerRunsPolicy = new ThreadPoolExecutor.CallerRunsPolicy(); threadPoolInfo.setRejectedExecutionHandler(callerRunsPolicy); threadPoolInfo.initialize(); return threadPoolInfo; } }
Use extended thread pool
Explicitly specify the Bean to use threadPoolInfo
@SpringBootTest class SpringbootApplicationTests { @Qualifier("threadPoolInfo") @Autowired private ThreadPoolTaskExecutor threadPoolTaskExecutor; @Test public void test() { //Submit 10 tasks to the thread pool for (int i = 0; i < 10; i++) { int finalI = i; threadPoolTaskExecutor.execute(new Runnable() { @Override public void run() { } }); } } }
INFO 27752 --- [ main] cn.ybzy.demo.config.ThreadPoolInfo : threadPoolInfo-,execute ,taskCout=1,completedTaskCount=0,activeCount=1,queueSize=0 INFO 27752 --- [ main] cn.ybzy.demo.config.ThreadPoolInfo : threadPoolInfo-,execute ,taskCout=2,completedTaskCount=1,activeCount=1,queueSize=0 INFO 27752 --- [ main] cn.ybzy.demo.config.ThreadPoolInfo : threadPoolInfo-,execute ,taskCout=3,completedTaskCount=2,activeCount=1,queueSize=0 INFO 27752 --- [ main] cn.ybzy.demo.config.ThreadPoolInfo : threadPoolInfo-,execute ,taskCout=4,completedTaskCount=3,activeCount=1,queueSize=0 INFO 27752 --- [ main] cn.ybzy.demo.config.ThreadPoolInfo : threadPoolInfo-,execute ,taskCout=5,completedTaskCount=4,activeCount=1,queueSize=0 INFO 27752 --- [ main] cn.ybzy.demo.config.ThreadPoolInfo : threadPoolInfo-,execute ,taskCout=6,completedTaskCount=4,activeCount=2,queueSize=0 INFO 27752 --- [ main] cn.ybzy.demo.config.ThreadPoolInfo : threadPoolInfo-,execute ,taskCout=7,completedTaskCount=6,activeCount=1,queueSize=0 INFO 27752 --- [ main] cn.ybzy.demo.config.ThreadPoolInfo : threadPoolInfo-,execute ,taskCout=8,completedTaskCount=6,activeCount=2,queueSize=0 INFO 27752 --- [ main] cn.ybzy.demo.config.ThreadPoolInfo : threadPoolInfo-,execute ,taskCout=9,completedTaskCount=8,activeCount=1,queueSize=0 INFO 27752 --- [ main] cn.ybzy.demo.config.ThreadPoolInfo : threadPoolInfo-,execute ,taskCout=10,completedTaskCount=9,activeCount=1,queueSize=0