使用前提
假如当前类 a.class 中有异步方法,并使用了@Async,那么必须由其他类(例如b.class)来调用,不可由其本身(a.class)来调用;
该方法必须是public 且无返回值,即:
@Async
public void asyncMethod(){
}
需要注意
在和事务一起使用的时候,即调用这个异步方法的方法上使用了@Transactional注解。
a.class
@Async
public void asyncMethod(){
}
b.class
@Transactional
public void fun(){
//...
a.asyncMethod();
//...
}
此时事务会有问题,因为a会新起一个线程来执行asyncMethod(),所以a与fun()不是一个事务,会导致数据不一致。
2.需要为异步配置线程池
如下
package com.fanneng.spring.config;
import lombok.Getter;
import lombok.Setter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.concurrent.ThreadPoolExecutor;
/**
* Created by feifan.gou@gmail.com on 2019/11/29 20:00.
*/
@Configuration
@EnableAsync
public class ThreadPoolConfig {
@Autowired
private ThreadPoolProperty poolProperty;
@Bean
public ThreadPoolTaskExecutor taskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
/* 最大线程数 */
executor.setMaxPoolSize(poolProperty.maxPoolSize);
/* 核心线程数 */
executor.setCorePoolSize(poolProperty.corePoolSize);
/* 队列最大长度 */
executor.setQueueCapacity(poolProperty.queueCapacity);
/* 线程名称前缀 */
executor.setThreadNamePrefix(poolProperty.threadNamePrefix);
/* 拒绝策略 */
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
executor.initialize();
return executor;
}
@Getter
@Setter
@Configuration
@ConfigurationProperties(prefix = "executor.thread.pool")
public static class ThreadPoolProperty {
private int corePoolSize;
private int maxPoolSize;
private int queueCapacity;
private String threadNamePrefix;
}
/* 拒绝策略说明(当pool已经达到max size的时候,如何处理新任务):
* ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。
* ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常。
* ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)
* ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务
*/
}
在注入ThreadPoolTaskExecutor的时候需要注意
public ThreadPoolTaskExecutor taskExecutor()中的方法名默认是taskExecutor,那么意味着bean的名称也是taskExecutor,那么在使用@Async的时候,会默认去该线程池那线程;
为啥默认是taskExecutor呢? 请看spring源码 TaskExecutionAutoConfiguration.java 84行:
如果起了其他的名称,也就是bean是其他名称,那么在使用@Async的时候需要指定线程池的名称;例如bean的名称是 asyncExector ,那么使用@Async的时候应该是
@Async("asyncExector")
public void asyncMethod(){
}
否则会去默认使用 SimpleAsyncTaskExecutor。
附上spring 中 TaskExecutor 的常用几个实现
名字
特点
SimpleAsyncTaskExecutor
每次请求新开线程,没有最大线程数设置.不是真的线程池,这个类不重用线程,每次调用都会创建一个新的线程。 --【1】
SyncTaskExecutor
不是异步的线程.同步可以用SyncTaskExecutor,但这个可以说不算一个线程池,因为还在原线程执行。这个类没有实现异步调用,只是一个同步操作。
ConcurrentTaskExecutor
Executor的适配类,不推荐使用。如果ThreadPoolTaskExecutor不满足要求时,才用考虑使用这个类。
SimpleThreadPoolTaskExecutor
监听Spring’s lifecycle callbacks,并且可以和Quartz的Component兼容.是Quartz的SimpleThreadPool的类。线程池同时被quartz和非quartz使用,才需要使用此类。
ThreadPoolTaskExecutor
最常用。要求jdk版本大于等于5。可以在程序而不是xml里修改线程池的配置.其实质是对java.util.concurrent.ThreadPoolExecutor的包装。