在SpringBoot的日常开发中,一般都是==同步==调用的,但经常有特殊业务需要做==异步==来处理。
比如:注册用户、需要送积分、发短信和邮件、或者下单成功、发送消息等等。

同步和异步调用的区别

同步:按顺序执行,必须前面执行完成以后,后面才能继续执行,后面执行的过程中,也是需要等待的。
异步:按顺序执行,前面执行完成以后,可以直接返回成功。不需要等待后面执行完成。

常规的异步调用方法

new Thread(new Runnable() {
            @Override
            public void run() {
                LOGGER.info("异步执行.....");
            }
        }).start();

弊端:

1、每次new Thread新建对象性能差。
2、线程缺乏统一管理,可能无限制新建线程,相互之间出现竞争,极可能占用过多系统资源导致死机或者OOM(内存溢出)。
3、缺乏更多功能,比如:定时定时执行,定期执行,线程中断。

使用Async进行异步操作

@Async介绍

在Spring中,基于@Async标注的方法,称之为异步方法;这些方法将在执行的时候,会在独立的线程中被执行,调用者无需等待它的完成,即可继续其他的操作。

如何使用@Async

在启动类或者线程池配置类上加注解 @EnableAsync**

@SpringBootApplication
// 开启异步执行
@EnableAsync
public class XFBlogApplication {
    public static void main(String[] args) {
        SpringApplication.run(XFBlogApplication.class, args);
    }
}

2、定义了线程池的属性类

import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.ThreadPoolExecutor;

import org.apache.commons.lang3.concurrent.BasicThreadFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

/**
 * @className: ThreadPoolConfig
 * @description: 线程池配置
 * @author: xiaofei
 */
@Configuration
public class ThreadPoolConfig {

    /**
     * 核心线程池大小
     */
    private int corePoolSize = 50;

    /**
     * 最大可创建的线程数
     */
    private int maxPoolSize = 200;

    /**
     * 队列最大长度
     */
    private int queueCapacity = 1000;

    /**
     * 线程池维护线程所允许的空闲时间
     */
    private int keepAliveSeconds = 300;

    /**
     * 线程池中的线程的名称前缀
     */
    private String threadNamePrefix = "async-thread-";

    @Bean(name = "threadPoolTaskExecutor")
    public ThreadPoolTaskExecutor threadPoolTaskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        // 1: 创建核心线程数 cpu核数 -- 50
        executor.setCorePoolSize(corePoolSize);
        // 2:线程池维护线程的最大数量,只有在缓存队列满了之后才会申请超过核心线程数的线程
        executor.setMaxPoolSize(maxPoolSize);
        // 3:缓存队列 可以写大一点无非就浪费一点内存空间
        executor.setQueueCapacity(queueCapacity);
        // 4:线程的空闲事件,当超过了核心线程数之外的线程在达到指定的空闲时间会被销毁 200ms
        executor.setKeepAliveSeconds(keepAliveSeconds);
        // 5:线程池中的线程的名称前缀
        executor.setThreadNamePrefix(threadNamePrefix);
        /* 当线程的任务缓存队列已满并且线程池中的线程数量已经达到了最大连接数,如果还有任务来就会采取拒绝策略,
         * 通常有四种策略:
         *ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出异常:RejectedExcutionException异常
         *ThreadPoolExecutor.DiscardPolicy:丢弃任务,但是不抛出异常
         *ThreadPoolExecutor.DiscardOldestPolicy: 丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)
         *ThreadPoolExecutor.CallerRunsPolicy:重试添加当前的任务,自动重复调用execute()方法,直到成功。
         *ThreadPoolExecutor. 扩展重试3次,如果3次都不充公在移除。
         * */
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        return executor;
    }
}

需要异步的方法上面加上 @Async

无返回值的方法。

@Async("threadPoolTaskExecutor")
public void asyncMethod() throws Exception {
    Thread.sleep(5000);
    LOGGER.info("线程名称: " + Thread.currentThread().getName());
    LOGGER.info("无返回值-异步执行完毕!!!");
}

有返回值的方法。

/**
     * 有返回值的异步任务
     *
     * @throws Exception
     */
    @Async("threadPoolTaskExecutor")
    public Future<String> asyncMethodWithReturnType() {
        LOGGER.info("线程名称: " + Thread.currentThread().getName());
        try {
            Thread.sleep(5000);
            // 返回
            return new AsyncResult<>("带返回值-异步执行完毕 !!!!");
        } catch (InterruptedException e) {
            return null;
        }
    }

会让@Async注解失效的情况

1、异步方法使用static修饰。
2、异步类没有使用@Component注解(或其他注解)导致spring无法扫描到异步类(因为@Async是spring的注解)。
3、异步方法不能与异步方法在同一个类中。
4、类中需要使用@Autowired@Resource等注解自动注入,不能自己手动new对象(就以上例来说,得注入service,而不能new)。
5、如果使用SpringBoot框架必须在启动类中/或者线程池固定属性类中,增加@EnableAsync注解。
6、在Async 方法上标注@Transactional是没用的。 在Async 方法调用的方法上标注@Transactional 有效。
7、调用被@Async标记的方法的调用者不能和被调用的方法在同一类中不然不会起作用。
8、使用@Async时要求是不能有返回值的不然会报错的 因为异步要求是不关心结果的。

如何解决事务和异步之间的矛盾

方法A,使用了@Async/@Transactional来标注,但是无法产生事务控制的目的。
方法B,使用了@Async来标注, B中调用了C、D,C/D分别使用@Transactional做了标注,则可实现事务控制的目的。