springboot2.x basic tutorial: @Async opens asynchronous tasks

In development projects, we usually have scenarios where we need to start asynchronous tasks. For example, when the user registers successfully, some coupons need to be issued. At this time, in order to prevent these additional operations from affecting the user's registration process, we usually start a thread asynchronously to execute the coupon distribution logic.
Usually we need to define a thread pool ourselves and start a thread task. It is simplified in Springboot, and an org.springframework.core.task.TaskExecutor type task thread pool is automatically configured. When we enable @EnableAsync annotation, when we need to perform asynchronous tasks, add @Async annotations, this method Will automatically start a thread to execute.
When explaining how to configure Springboot to start asynchronous tasks, let's briefly explain the basic knowledge of Java thread pool.

Basic knowledge of thread pool

Use thread pool scenarios

Usually high concurrency, the task execution time is short, the thread creation time is T1, the task execution time is T2, and the thread destruction time is T3. When the time of T1+T2 is much greater than T2, the thread pool can be used. The core function is to reuse threads and reduce the time overhead of thread creation and destruction.
Conveniently count task execution information, such as the number of completed tasks.

The top-level interface of the Executor thread pool

After Jdk1.5 version, thread pool is provided for developers to easily create their own multi-threaded tasks. The java.util.concurrent.Executor interface is the top-level interface of the thread pool.
The interface execute receives a Runnable thread instance to execute a task.

public interface Executor {
    void execute(Runnable command);
}

ExecutorService interface detailed explanation

public interface ExecutorService extends Executor {

    /**关闭线程池不会接收新的任务,内部正在跑的任务和队列里等待的任务,会执行完
     */
    void shutdown();

    /**
     * 关闭线程池,不会接收新的任务
	 * 尝试将正在跑的任务interrupt中断
	 * 忽略队列里等待的任务
	 * 返回未执行的任务列表
     */
    List<Runnable> shutdownNow();

    /**
     * 返回线程池是否被关闭
     */
    boolean isShutdown();

    /**
     *线程池的任务线程是否被中断
     */
    boolean isTerminated();

    /**
	*接收timeout和TimeUnit两个参数,用于设定超时时间及单位。
	* 当等待超过设定时间时,会监测ExecutorService是否已经关闭,若关闭则返回true,否则返回false。和shutdown方法组合使用。
     */
    boolean awaitTermination(long timeout, TimeUnit unit)
        throws InterruptedException;

    /**
	 * 提交任务会获取线程的执行结果
     */
    <T> Future<T> submit(Callable<T> task);

    /**
	* 提交任务会获取线程的T类型的执行结果
     */
    <T> Future<T> submit(Runnable task, T result);

    /**
     * 提交任务
     */
    Future<?> submit(Runnable task);

    /**
     * 批量提交返回任务执行结果
     */
    <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
        throws InterruptedException;

    /**
     * 批量提交返回任务执行结果
	 *增加了超时时间控制,这里的超时时间是针对的所有tasks,而不是单个task的超时时间。
	 *如果超时,会取消没有执行完的所有任务,并抛出超时异常
     */
    <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks,
                                  long timeout, TimeUnit unit)
        throws InterruptedException;

    /**
     * 取得第一个方法的返回值,当第一个任务结束后,会调用interrupt方法中断其它任务
     */
    <T> T invokeAny(Collection<? extends Callable<T>> tasks)
        throws InterruptedException, ExecutionException;

    /**
     * 取得第一个方法的返回值,当第一个任务结束后,会调用interrupt方法中断其它任务
	 * 任务执行超时抛出异常
     */
    <T> T invokeAny(Collection<? extends Callable<T>> tasks,
                    long timeout, TimeUnit unit)
        throws InterruptedException, ExecutionException, TimeoutException;
}

ThreadPoolExecutor parameter analysis

ThreadPoolExecutor is an interface used to process asynchronous tasks. It can be understood as a thread pool and a task queue. The tasks submitted to the ExecutorService object will be put into the task team or directly executed by threads in the thread pool. ThreadPoolExecutor supports adjustments Construct parameters to configure different task processing strategies.
The ThreadPoolExecutor thread pool constructor code is posted below:

    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {
        if (corePoolSize < 0 ||
            maximumPoolSize <= 0 ||
            maximumPoolSize < corePoolSize ||
            keepAliveTime < 0)
            throw new IllegalArgumentException();
        if (workQueue == null || threadFactory == null || handler == null)
            throw new NullPointerException();
        this.acc = System.getSecurityManager() == null ?
                null :
                AccessController.getContext();
        this.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
    }

The meaning of each input parameter of ThreadPoolExecutor: The relationship between the parameters:

  1. Obtain available threads from the thread pool to perform tasks. If there are no available threads, use ThreadFactory to create new threads until the number of threads reaches the corePoolSize limit
  2. After the number of threads in the thread pool reaches corePoolSize, new tasks will be put into the queue until the queue can no longer accommodate more tasks
  3. When the queue can no longer hold more tasks, a new thread will be created until the number of threads reaches the maxinumPoolSize limit
  4. After the number of threads reaches the maxinumPoolSize limit, the new task will be rejected for execution, and the RejectedExecutionHandler will be called for processing

RejectedExecutionHandler rejection strategy

1. AbortPolicy: throw an exception directly, the default policy;
2. CallerRunsPolicy: use the thread of the caller to execute the task;
3. DiscardOldestPolicy: discard the first task in the blocking queue and execute the current task;
4. DiscardPolicy: discard directly task;

Key points of using ThreadPoolExecutor

  1. Reasonably configure the size of the thread pool: If it is a CPU-intensive task, the number of thread pools can be set to the number of CPU cores + 1, if it is an IO-intensive task, the CPU will be more idle, and the number of thread pools should be set to the number of CPU cores*2
  2. Task queue: The task queue generally uses LinkedBlockingQueue to specify the size, and is used in conjunction with the rejection strategy. The default is Integer.Max_VALUE, which is prone to memory overflow.
  3. Task exception: add try{}catch{} to catch the exception in the task attention in the thread pool, which is convenient for troubleshooting

SpringBoot thread pool

org.springframework.core.task.TaskExecutor is the interface class of Spring asynchronous thread pool, and its essence is java.util.concurrent.Executor. The thread pool used by springboot2.3.x by default is ThreadPoolTaskExecutor, which can be easily adjusted through TaskExecutionProperties



SpringBoot defines its own asynchronous thread pool

@EnableAsync
@Component
@Slf4j
public class AsyncConfig implements AsyncConfigurer {
    public TaskExecutor taskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        // 设置核心线程数
        executor.setCorePoolSize(8);
        // 设置最大线程数
        executor.setMaxPoolSize(16);
        // 设置队列容量
        executor.setQueueCapacity(50);
        // 设置线程活跃时间(秒)
        executor.setKeepAliveSeconds(60);
        //设置线程池中任务的等待时间,如果超过这个时候还没有销毁就强制销毁,以确保应用最后能够被关闭,而不是阻塞住
        executor.setAwaitTerminationSeconds(60);
        // 设置默认线程名称
        executor.setThreadNamePrefix("CodehomeAsyncTask-");
        // 设置拒绝策略
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        // 等待所有任务结束后再关闭线程池
        executor.setWaitForTasksToCompleteOnShutdown(true);
        executor.initialize();
        return executor;
    }
    public Executor getAsyncExecutor() {
        return taskExecutor();
    }
    public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
        return new MyAsyncExceptionHandler();
    }

    /**
     * 自定义异常处理类
     */
    class MyAsyncExceptionHandler implements AsyncUncaughtExceptionHandler {

        @Override
        public void handleUncaughtException(Throwable throwable, Method method, Object... objects) {
            log.info("Exception message - " + throwable.getMessage());
            log.info("Method name - " + method.getName());
            for (Object param : objects) {
                log.info("Parameter value - " + param);
            }
        }
    }
}

Two ways to use


@Component
@Slf4j
public class UserServiceSyncTask {
	//不带返回值
    @Async
    public void sendEmail(){
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        log.info(Thread.currentThread().getName());
    }
	//带返回值
    @Async
    public Future<String> echo(String msg){
        try {
            Thread.sleep(5000);
            return new AsyncResult<String>(Thread.currentThread().getName()+"hello world !!!!");
        } catch (InterruptedException e) {
            //
        }
        return null;
    }
}

A thousand miles begins with a single step. Here is the tenth article of the SpringBoot tutorial series. All project source codes can be downloaded on my GitHub .

Guess you like

Origin blog.csdn.net/github_35592621/article/details/108248916