Spring Boot thread asynchronous call and the use of thread pool

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

Tags: Java Spring Boot Multithreading thread pool asynctask

Posted on Sun, 07 Nov 2021 21:21:53 -0500 by passagewds