Spring 비동기 주석 @Async 및 스레드 풀 구성(소스 코드 분석)

Spring 비동기 주석 @Async 및 스레드 풀 구성

@Async 주석을 사용하면 기본적으로 SimpleAsyncTaskExecutor 스레드 풀이 사용됩니다. 이 스레드 풀은 진정한 의미의 스레드 풀이 아닙니다. 이 스레드 풀을 사용하면 스레드 재사용을 달성할 수 없으며 호출할 때마다 새 스레드가 생성됩니다. 시스템이 스레드를 계속 생성하면 결국 시스템이 너무 많은 메모리를 차지하게 되고 OutOfMemoryError 오류가 발생하게 됩니다.

SimpleAsyncTaskExecutor 소스 코드 분석(일부 코드 생략)

public class SimpleAsyncTaskExecutor extends CustomizableThreadCreator
		implements AsyncListenableTaskExecutor, Serializable {
    
    

	//ps: 这个参数值在 AsyncListenableTaskExecutor 的父类 AsyncTaskExecutor 里面
	/** Constant that indicates no time limit. */
	long TIMEOUT_INDEFINITE = Long.MAX_VALUE;

	//ps: execute 方法只有两个,不带时间的方法的调用带时间的,并且这个时间是Long类型的最大值
	/**
	 * Executes the given task, within a concurrency throttle
	 * if configured (through the superclass's settings).
	 * @see #doExecute(Runnable)
	 */
	@Override
	public void execute(Runnable task) {
    
    
		execute(task, TIMEOUT_INDEFINITE);
	}

	//ps: 调用这个方法,不管判断逻辑怎样,最终都会调用 doExecute 方法
	/**
	 * Executes the given task, within a concurrency throttle
	 * if configured (through the superclass's settings).
	 * <p>Executes urgent tasks (with 'immediate' timeout) directly,
	 * bypassing the concurrency throttle (if active). All other
	 * tasks are subject to throttling.
	 * @see #TIMEOUT_IMMEDIATE
	 * @see #doExecute(Runnable)
	 */
	@Override
	public void execute(Runnable task, long startTimeout) {
    
    
		Assert.notNull(task, "Runnable must not be null");
		Runnable taskToUse = (this.taskDecorator != null ? this.taskDecorator.decorate(task) : task);
		if (isThrottleActive() && startTimeout > TIMEOUT_IMMEDIATE) {
    
    
			this.concurrencyThrottle.beforeAccess();
			doExecute(new ConcurrencyThrottlingRunnable(taskToUse));
		}
		else {
    
    
			doExecute(taskToUse);
		}
	}

	//ps: doExecute 方法只有一个,所有的最终都是回到这个方法, 最终调用 thread.start() 来启动这个线程,最后被分配时间片去执行run方法
	//可以看到,不管怎样都是新建一个线程,只不过是新建线程的方式不一样, 如果线程工厂类不为空就使用工厂创建线程,否则使用父类的创建线程的方法来创建线程
	/**
	 * Template method for the actual execution of a task.
	 * <p>The default implementation creates a new Thread and starts it.
	 * @param task the Runnable to execute
	 * @see #setThreadFactory
	 * @see #createThread
	 * @see java.lang.Thread#start()
	 */
	protected void doExecute(Runnable task) {
    
    
		Thread thread = (this.threadFactory != null ? this.threadFactory.newThread(task) : createThread(task));
		thread.start();
	}


	//ps: 这个方法在当前类的父类 CustomizableThreadCreator 里面
	//这个方法是新建一个线程
	/**
	 * Template method for the creation of a new {@link Thread}.
	 * <p>The default implementation creates a new Thread for the given
	 * {@link Runnable}, applying an appropriate thread name.
	 * @param runnable the Runnable to execute
	 * @see #nextThreadName()
	 */
	public Thread createThread(Runnable runnable) {
    
    
		Thread thread = new Thread(getThreadGroup(), runnable, nextThreadName());
		thread.setPriority(getThreadPriority());
		thread.setDaemon(isDaemon());
		return thread;
	}

}

@Async 비동기 주석의 부분 구현을 살펴보면,
@EnableAsync를 찾아 보면 비동기 주석을 활성화하는 문서에 비동기 주석이 사용하는 스레드 풀이 org.springframework.core.task.SimpleAsyncTaskExecutor라고 명시되어 있음을 알 수 있습니다.

또한 비동기 메서드 호출을 처리하는 데 이 스레드 풀이 비동기 메서드를 처리하는 데 사용된다는 문장이 사용됩니다.
여기에 이미지 설명을 삽입하세요

비동기 처리를 위한 구성 인터페이스 클래스 AsyncConfigurer
여기에 이미지 설명을 삽입하세요

AsyncConfigurer 구현 클래스 AsyncConfigurerSupport
여기에 이미지 설명을 삽입하세요

다음으로 비동기 스레드 풀을 사용자 정의합니다.

비동기 스레드 풀 구성 인터페이스 클래스를 구현하고 기본 스레드 풀 구성을 사용자 정의 ThreadPoolTaskExecutor로 변경합니다.

package com.biostime.material.purchase.config.log;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.task.SimpleAsyncTaskExecutor;
import org.springframework.scheduling.annotation.AsyncConfigurer;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

import java.util.concurrent.Executor;
import java.util.concurrent.ThreadPoolExecutor;

/**
 * @Description Async 异步注解线程配置, AsyncConfigurer 默认配置实现,默认的线程池 SimpleAsyncTaskExecutor
 * @author 21506 HuangXiang
 * @since 2021/12/1
 */
@Configuration
public class AsyncConfiguration implements AsyncConfigurer {
    
    

    private static final Logger LOGGER = LogManager.getLogger(LogManager.ROOT_LOGGER_NAME);

    //线程池配置
    @Bean(name = "asyncPoolTaskExecutor")
    public ThreadPoolTaskExecutor executor(){
    
    
        ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();

        //核销线程数
        taskExecutor.setCorePoolSize(2);
        //线程池维护线程的最大数量,只有在缓冲队列满了之后才会申请超过核心线程数的线程
        taskExecutor.setMaxPoolSize(10);
        //缓存队列
        taskExecutor.setQueueCapacity(50);
        //允许的空闲时间,当超过了核心线程出之外的线程在空闲时间到达之后会被销毁
        taskExecutor.setKeepAliveSeconds(200);
        //异步方法内部线程名称
        taskExecutor.setThreadNamePrefix("async-");
        /**
         * 当线程池的任务缓存队列已满并且线程池中的线程数目达到maximumPoolSize,如果还有任务到来就会采取任务拒绝策略
         * 通常有以下四种策略:
         * ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。
         * ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常。
         * ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)
         * ThreadPoolExecutor.CallerRunsPolicy:重试添加当前的任务,自动重复调用 execute() 方法,直到成功
         */
        taskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        taskExecutor.initialize();

        return taskExecutor;

    }


    //指定默认线程池
    @Override
    public Executor getAsyncExecutor() {
    
    
        return executor();
    }

    //线程异常处理
    @Override
    public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
    
    

        return (ex , method , params) ->
                LOGGER.error("线程池执行任务发送未知错误,执行方法:{}",method.getName(),ex);
    }
}

다음으로, 비동기 스레드 호출은 사용자 정의 스레드 풀을 사용합니다!

추천

출처blog.csdn.net/HX0326CSDN/article/details/121655391