简介
在Spring当中,被@Async注解标记的方法,称为异步方法,这些方法会在线程当中独立执行,开发人员无需等待它的完成,可以继续其他的业务操作;Spring容器在初始化Bean时,会先判断Bean中是否使用了@Async注解,创建切点,根据切点创建代理,在调用@Async注解标注的方法时,会调用代理,执行切点的Invoke方法,将方法的执行交给线程池进行异步执行。
@Async原理
当一个方法被@Async标记时,Spring会通过其内部的TaskExecutor(任务执行器,默认是SimpleAsyncTaskExecutor或者基于ThreadPoolTaskExecutor的变体)来异步执行这个方法。这意味着调用该方法后,控制权会立即返回给调用者,而实际的方法逻辑会在另外的线程中并行执行。
非线程池模型
与常见的线程池(如ThreadPoolTaskExecutor)不同,SimpleAsyncTaskExecutor每次执行一个任务时都会创建一个新的线程。这意味着对于每个异步方法调用,它都会生成一个新的线程来执行该任务,执行完后,这个线程不会被复用,而是会被JVM的线程管理机制回收。
适用场景
由于其不复用线程的特性,SimpleAsyncTaskExecutor特别适合用于那些执行次数不多、且对线程创建开销不敏感的轻量级异步任务。如果任务执行频繁且持续时间短,频繁创建和销毁线程可能会导致性能问题和资源浪费。
无限制
它没有核心线程数、最大线程数、队列等概念,因此不会受到线程池大小或队列容量的限制。这对于某些特定场景可能是有利的,例如当不确定任务的数量或不想受限于固定大小的线程池时。
@Async注意事项
资源消耗:由于每次执行任务都新建线程,如果异步任务被频繁调用,可能会迅速消耗大量系统资源(如内存),导致性能下降甚至系统崩溃。
不适合高并发:对于高并发场景,不建议使用SimpleAsyncTaskExecutor,因为它缺乏有效的线程管理和任务排队机制,可能会引发大量的线程创建,严重时会导致OutOfMemoryError。
单次使用:SimpleAsyncTaskExecutor设计初衷是针对那些执行后不再复用的异步任务。如果你的应用需要处理大量并发或频繁的异步请求,应考虑使用基于线程池的ThreadPoolTaskExecutor或其他更高级的线程管理策略。
综上所述,虽然SimpleAsyncTaskExecutor简单易用,但在大多数生产环境中,出于性能和资源管理的考虑,推荐使用基于线程池的执行器来替代它,除非你能确定应用的负载和场景适合使用这种非池化的线程创建方式。
不带有返回值和带有返回值的异步任务
1,创建Controller类
@RestController
@RequestMapping("/syncTest")
public class AsyncController {
@Autowired
private TestSyncService syncService;
@RequestMapping("/getAll")
public void getUsers(){
System.out.println("业务开始");
syncService.getUsers();
System.out.println("业务结束");
}
@RequestMapping("/getAll2")
public String getUsers2() throws ExecutionException, InterruptedException {
System.out.println("业务开始");
Future<String> future = syncService.getUsers2();
System.out.println("业务结束");
return future.get();
}
}
2,创建Service类,模拟业务操作
@Service
public class TestSyncServiceImpl implements TestSyncService {
/**
*不带有返回值异步任务
*/
@Async
@Override
public void getUsers() {
try {
Thread.sleep(2000);
System.out.println("查询到了所有的用户信息!");
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
/**
*带有返回值异步任务
*/
@Async
@Override
public Future<String> getUsers2() {
try {
Thread.sleep(2000);
System.out.println(Thread.currentThread().getName()+"查询到了所有的用户信息!");
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
return new AsyncResult<>("异步任务执行好了");
}
}
3,启动类当中加上@EnableAsync注解,开启异步任务
@SpringBootApplication
@EnableAsync
public class SpringbootApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootApplication.class, args);
}
}
4,调用结果
http://localhost:8898/syncTest/getAll
业务开始
业务结束
查询到了所有的用户信息!
http://localhost:8898/syncTest/getAll2
业务开始
业务结束
task-1查询到了所有的用户信息!
Writing ["异步任务执行好了"]
自定义线程池
编写ExecutorConfig自定义配置类
@Configuration
public class ExecutorConfig {
@Bean("htt")
public Executor taskExecutor(){
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
//核心线程数5:
executor.setCorePoolSize(5);
//最大线程数10:线程池最大的线程数,只有在缓冲队列满了之后才会申请超过核心线程数的线程
executor.setMaxPoolSize(10);
//缓冲队列100:用来缓冲执行任务的队列
executor.setQueueCapacity(100);
//允许线程的空闲时间30秒:当超过了核心线程出之外的线程在空闲时间到达之后会被销毁
executor.setKeepAliveSeconds(30);
//线程池对拒绝任务的处理策略:这里采用了CallerRunsPolicy策略,当线程池没有处理能力的时候,该策略会直接在 execute 方法的调用线程中运行被拒绝的任务;
//如果执行程序已关闭,则会丢弃该任务
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
executor.setThreadNamePrefix("htt-");
return executor;
}
}
修改Service类,在@Async注解指定Bean名称,从而可以使用我们自定义的线程池
@Service
public class TestSyncServiceImpl implements TestSyncService {
/**
* @Async指定自定义线程池
*/
@Async("htt")
@Override
public void getUsers3() {
try {
Thread.sleep(2000);
System.out.println(Thread.currentThread().getName()+"查询到了所有的用户信息!");
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
运行结果
业务开始
业务结束
htt-1查询到了所有的用户信息!
常见失效场景
1,主启动类没有添加@EnableAsync注解
2,@Async注解是基于Spring的AOP动态代理模式实现的,适用于一切事务失效情景,比如@Async注解修饰的方法不是public修饰、方法被this调用而不是注入的对象调用、类没有交给spring管理、或者new出来的对象调用等等