@Async asynchronous thread: Spring's own asynchronous solution

foreword 

        In project applications, using MQ asynchronous calls to optimize system performance and complete data synchronization between services is a common technical means. If it is within the same server, does not involve a distributed system, and simply wants to implement asynchronous execution of some services, here is a simpler asynchronous method call.

        For asynchronous method calls, the @Async annotation has been provided since Spring3, which can be marked on a method to call the method asynchronously. The caller will return immediately when called, and the actual execution of the method will be submitted to the task of Spring TaskExecutor, which will be executed by the thread in the specified thread pool.

        This article describes the simple application of the @Async annotation in the Spring system, for learning purposes only, and welcomes feedback.  


text

1. Classification of Spring thread pool

        The following are the five common TaskExecuters that have been officially implemented. Spring claims that these TaskExecuters are perfectly sufficient for any scenario:

thread features
SimpleAsyncTaskExecutor Every time a new thread is requested, there is no setting for the maximum number of threads. It is not a real thread pool, this class does not reuse threads, and each call will create a new thread
SyncTaskExecutor Not asynchronous threads. Synchronization can use SyncTaskExecutor, but this can not be said to be a thread pool, because it is still executed in the original thread. This class does not implement an asynchronous call, just a synchronous operation.
ConcurrentTaskExecutor The adaptation class of Executor is not recommended. Only consider using this class if ThreadPoolTaskExecutor does not meet the requirements.
SimpleThreadPoolTaskExecutor Is Quartz's SimpleThreadPool class. This class is required only when the thread pool is used by both quartz and non-quartz.
ThreadPoolTaskExecutor The most commonly used and recommended is the thread class specified in the Alibaba Java Development Specification, and the jdk version is required to be greater than or equal to 5. Its essence is the packaging of java.util.concurrent.ThreadPoolExecutor.

       Refer to the Alibaba java development specification. In the thread pool application: the thread pool is not allowed to use Executors to create, nor is it allowed to use the system default thread pool. It is recommended to use ThreadPoolExecutor . This processing method makes the development engineer more clear about the thread pool operating rules to avoid the risk of resource exhaustion.

2. Use @Async in SpringBoot

        Using an asynchronous thread to call a method, the process is as follows:

  • Write a configuration class and define a thread pool
  • Annotate the startup class/configuration file: @EnableAsync
  • Annotate the method: @Async

        In the demo case below, the directory of my local project is for reference only:

2.1 Enable @Async

        Key annotation @EnableAsync ! ! ! It can be loaded on the startup class or added to the configuration file, the effect is the same.

  •  Method 1: Enable based on the Springboot startup class
@EnableAsync
@SpringBootApplication
public class AsyncApplication {

    public static void main(String[] args) {
        SpringApplication.run(AsyncApplication.class, args);
    }

}
  • Method 2: Enable based on Java configuration
// com.example.async.service 为即将开启异步线程业务的包位置(后面有详细讲解)
@EnableAsync
@Configuration
@ComponentScan("com.example.async.service")
public class AsyncConfiguration implements AsyncConfigurer {

    @Override
    public Executor getAsyncExecutor() {
        return executor();
    }
    ...
}

2.2 @Async and thread pool

        Spring applies the default thread pool, which means that the name of the thread pool is not specified when the @Async annotation is used. View the source code, the default thread pool of @Async is SimpleAsyncTaskExecutor .

@Slf4j
@Service
public class BusinessServiceImpl implements BusinessService {

    /**
     * 方法4:没有指定线程池,验证默认线程池也ok(不推荐:规避资源耗尽的风险)
     */
    @Async
    public void asyncDemo4() {
        log.info("asyncDemo4:" + Thread.currentThread().getName() + " 正在执行 ----------");
        try {
            Thread.sleep(2*1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        log.info("asyncDemo4:" + Thread.currentThread().getName() + " 执行结束!!");
    }
}

2.3 @Async custom thread pool

        Customize the thread pool, which can control the thread pool in the system more fine-grained, facilitate the adjustment of the thread pool size configuration, and thread execution exception control and processing. When setting the system custom thread pool to replace the default thread pool, although it can be set in multiple modes, there is only one thread pool that will be generated after replacing the default thread pool (you cannot set multiple classes to inherit AsyncConfigurer).

        The custom thread pool has the following modes:

  1. Reimplement the interface AsyncConfigurer;
  2. Inherit AsyncConfigurerSupport;
  3. Configure a custom TaskExecutor to replace the built-in task executor;

        The usage methods of the three are roughly the same, and the following case will show one of them: the way of implementing the AsyncConfigurer interface.

  • Configure a thread pool ThreadPoolTaskExecutor
/**
 * com.example.async.service:即将开启异步线程的业务方法是哪个
 *
 * 解释:
 *  1.即将开启异步线程业务的包位置:com.example.async.service
 *  2.通过 ThreadPoolExecutor 的方式,规避资源耗尽的风险
 */
@EnableAsync
@Configuration
@ComponentScan("com.example.async.service")
public class AsyncConfiguration implements AsyncConfigurer {

    @Override
    public Executor getAsyncExecutor() {
        return executor();
    }

    /**
     * 执行需要依赖线程池,这里就来配置一个线程池
     * 1.当池子大小小于corePoolSize,就新建线程,并处理请求
     * 2.当池子大小等于corePoolSize,把请求放入workQueue(QueueCapacity)中,池子里的空闲线程就去workQueue中取任务并处理
     * 3.当workQueue放不下任务时,就新建线程入池,并处理请求,如果池子大小撑到了maximumPoolSize,就用RejectedExecutionHandler来做拒绝处理
     * 4.当池子的线程数大于corePoolSize时,多余的线程会等待keepAliveTime长时间,如果无请求可处理就自行销毁
     */
    @Bean("asyncExecutor")
    public ThreadPoolTaskExecutor executor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        //设置线程名
        executor.setThreadNamePrefix("async-method-execute-");
        //设置核心线程数
        executor.setCorePoolSize(10);
        //设置最大线程数
        executor.setMaxPoolSize(50);
        //线程池所使用的缓冲队列
        executor.setQueueCapacity(100);
        //设置多余线程等待的时间,单位:秒
        executor.setKeepAliveSeconds(10);
        // 初始化线程
        executor.initialize();
        return executor;
    }
}
  • Execute the asynchronous thread method, specify the thread pool: value should be the same as the name in the configuration class Bean() 
/**
 * 异步线程 - 执行业务
 * 注意:
 *  1.@Async 注解调用用线程池,不指定的话默认:SimpleAsyncTaskExecutor
 *  2.SimpleAsyncTaskExecutor 不是真的线程池,这个类不重用线程,默认每次调用都会创建一个新的线程
 */
@Slf4j
@Service
public class AsyncServiceImpl implements AsyncService {
    /**
     * 方法1:@Async 标注为异步任务:执行此方法的时候,会单独开启线程来执行,不影响主线程的执行
     */
    @Async("asyncExecutor")
    public void asyncDemo1() {
        log.info("asyncDemo1:" + Thread.currentThread().getName() + " 正在执行 ----------");
        // 故意等10秒,那么异步线程开起来,这样明显看到:方法2不用等方法1执行完就调用了
        try {
            Thread.sleep(10*1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        log.info("asyncDemo1:" + Thread.currentThread().getName() + " 执行结束!!");
    }

    /**
     * 方法2:与方法1一起执行,证明2个线程异步执行,互不干扰
     */
    @Async("asyncExecutor")
    public void asyncDemo2() {
        log.info("asyncDemo2:" + Thread.currentThread().getName() + " 正在执行 ----------");
        try {
            Thread.sleep(5*1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        log.info("asyncDemo2:" + Thread.currentThread().getName() + " 执行结束!!");
    }

    /**
     * 方法3:没有指定线程池,验证默认线程池也ok(不推荐:规避资源耗尽的风险)
     */
    @Async
    public void asyncDemo3() {
        log.info("asyncDemo3:" + Thread.currentThread().getName() + " 正在执行 ----------");
        try {
            Thread.sleep(1*1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        log.info("asyncDemo3:" + Thread.currentThread().getName() + " 执行结束!!");
    }
}

2.4 Start the test

        Start the SpringBoot project through AsyncApplication, and Postman performs interface testing:

http://127.0.0.1:8080/async/demo

  • I wrote 4 demos to simulate 4 situations respectively. The details are written in the comments.
@Slf4j
@RestController
@RequestMapping("/async")
public class AsyncControllor {

    @Autowired
    private AsyncService asyncMethodService;
    @Autowired
    private BusinessService businessService;

    @GetMapping("/demo")
    public String demo()  {
        log.info("接口调用:【开始】 --------------------");
        try {
            // 执行异步任务 - 自定义线程池
            asyncMethodService.asyncDemo1();
            asyncMethodService.asyncDemo2();
            asyncMethodService.asyncDemo3();
            // 执行异步任务 - 默认线程池
            businessService.asyncDemo4();
        } catch (Exception e) {
            return "Exception";
        }
        log.info("接口调用:【结束】 --------------------");
        return "success";
    }
}
  • Running result: the interface execution ends, and the asynchronous thread is still running


Summarize

  1. @EnableAsync is the key to start the @Async asynchronous thread, it can be loaded on the startup class or added to the configuration file;
  2. In order to avoid the risk of resource exhaustion, it is recommended to create a thread pool through ThreadPoolExecutor ;
  3. The @Async annotation is annotated on the method to call the method asynchronously;

Guess you like

Origin blog.csdn.net/weixin_44259720/article/details/130292656