SpringBoot注解之@Async和自定义线程池

前言

我们在学习线程池的时候,都知道线程池的核心线程数、最大线程数、线程工厂等核心参数非常重要,故熟记于心。但是有些工作五六年的攻城狮可能说不出来怎么初始化一个全局线程池,以在不同场景使用;所以,本文基于Spring Boot的异步注解@Async自定义全局线程池。如果是这方面的老铁,请绕道而行,这里都是雕虫小技。

maven坐标

        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.7</version>
        </dependency>
        <dependency>
            <groupId>com.google.guava</groupId>
            <artifactId>guava</artifactId>
            <version>31.0.1-jre</version>
        </dependency>

自定义线程池

核心线程数和最大线程数等参数可以在配置文件中定义,这里为了简单,就直接赋值了。

import com.google.common.util.concurrent.ThreadFactoryBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.concurrent.*;

/**
 * @Author: Wiener
 * @Date: 2022/1/8 19:57
 */
@Configuration
public class AsyncTaskConfig  {

    // 包括corePoolSize线程在内,多余的线程会等待keepAliveTime长时间,如果无请求可处理就自行销毁
    @Bean("first-executor")
    public Executor firstExecutor() {
        ThreadFactory guavaFactory = new ThreadFactoryBuilder().setNameFormat("first-executor").build();
        int curSystemThreads = Runtime.getRuntime().availableProcessors() * 2;
        ThreadPoolExecutor threadPool = new ThreadPoolExecutor(curSystemThreads, 32,
                30L, TimeUnit.MILLISECONDS,
                new LinkedBlockingQueue<>(), guavaFactory);
        threadPool.allowsCoreThreadTimeOut();
        return threadPool;
    }
    //my-task用于指定线程池名字
    @Bean("my-executor")
    public Executor myExecutor() {
        ThreadFactory guavaFactory = new ThreadFactoryBuilder().setNameFormat("my-executor").build();
        int curSystemThreads = Runtime.getRuntime().availableProcessors() * 2;
        ThreadPoolExecutor threadPool = new ThreadPoolExecutor(curSystemThreads, 32,
                30L, TimeUnit.MILLISECONDS,
                new LinkedBlockingQueue<>(), guavaFactory);
        threadPool.allowsCoreThreadTimeOut();
        return threadPool;
    }

}

使用@Async注解创建线程任务

Spring为任务调度与异步方法执行提供了注解@Async支持,通过在方法上标注@Async注解,可使得方法被异步调用。在需要异步执行的方法上加入@Async注解,并指定使用的线程池,当然可以不指定,直接写@Async。

service定义如下:

/**
 *  指定pool
 * @Author: Wiener
 * @Date: 2022/1/8 19:59
 */
public interface AsyncTaskService {
    void executeAsyncTask(Integer i);
    void executeMyTask(Integer i);
}

实现类如下:

import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

/**
 * @Author: Wiener
 * @Date: 2022/1/9 12:33
 */
@Slf4j
@Service
public class AsyncTaskServiceImpl implements AsyncTaskService {

    @Async("first-executor")
    @Override
    public void executeAsyncTask(Integer i) {
        System.out.println("线程" + Thread.currentThread().getName() + " 执行异步任务:" + i);
        try {
            Thread.sleep(100);
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println("end executeAsync");
    }

    @Async("my-executor")
    @Override
    public void executeMyTask(Integer i) {
        System.out.println("---线程" + Thread.currentThread().getName() + " 执行异步任务:" + i);
        try {
            Thread.sleep(100);
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println("---end executeAsync");
    }

}

修改启动类和创建测试类

在启动类添加注解@EnableAsync,以支持异步操作。然后,创建一个controller:

import com.swagger.demo.service.AsyncTaskService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @author 测试线程池
 */
@Slf4j
@RestController
@RequestMapping("/pool")
public class GuavaController {
    @Autowired
    private AsyncTaskService asyncTaskService;

    @PostMapping("asyncTaskTest")
    public void asyncTaskTest() {
		// 请求参数写为固定值
        asyncTaskService.executeAsyncTask(1);
        asyncTaskService.executeMyTask(200);
        System.out.println("All tasks finished.");
    }

}

启动Spring Boot服务后,请求如上API,在控制台可以看到如下日志:

线程first-executor 执行异步任务:1
All tasks finished.
---线程my-executor 执行异步任务:200
end executeAsync
---end executeAsync

从执行结果来看,我们配置的两个执行器都成功了。

小结|@Async的使用方法

在使用@Async注解的时候,有以下几点需要注意,

  1. 返回值:若不需要返回值,则直接设置方法返回值为void;若需要,则返回值用AsyncResult或者CompletableFuture承接。
  2. 自定义执行器,例如:@Async(“theGivenExecutor”)。
  3. 如何开启异步:@Async注解必须在不同类间调用,如 A类—>B类.methodC()(@Async添加在B类的方法methodC上)。若在同一个类中进行方法嵌套调用,会变同步执行,例如:A类.methodB()—>A类.@Async methodC()。
  4. @Async加到类上,表示这个类的所有方法都是异步执行,并且方法上的注解会覆盖类上的注解。慎用此种使用注解的方式!

老铁们, 因个人能力有限,难免有瑕疵,如果发现bug或者有更好的建议,那么请在文章下方留言!

 

5、异步失效的原因

  1. 注解@Async的方法不是public方法;
  2. 注解@Async的返回值只能为void或Future;
  3. 注解@Async方法使用static修饰也会失效;
  4. 没加@EnableAsync注解;
  5. 调用方和@Async不能在一个类中;
  6. 在Async方法上标注@Transactional是没用的,但在Async方法调用的方法上标注@Transcational是有效的;

Reference

https://www.jianshu.com/p/832f2b162450

大数据Excel导入数据库中,使用双异步,从191s优化到2s

扫码领红包

微信赞赏支付宝扫码领红包

发表回复

后才能评论