Springboot中使用@Async开启异步功能
何为异步?
同步:多个任务存在先后关系,必须按顺序一个接着一个任务执行
异步:当前任务在执行,后面的任务不必等当前任务完成才执行,可以同时执行
==简单来说:==在异步调用的过程中,一个任务的执行不会阻塞其他任务的执行,而是将任务提交给异步执行的机制,让程序能够继续执行其他操作。
能解决何问题?
异步能够解决许多并发和并行的问题,特别是在处理一些耗时的操作时,能够提高程序的效率和性能。
常用的场景有:
- 网络请求:在进行网络请求时,通常需要等待服务器响应。如果使用同步调用,程序会阻塞等待响应返回,影响程序的响应性和并发性。使用异步调用,可以在等待响应的同时执行其他任务,从而提高程序的效率和性能。
- 文件读写:在进行文件读写时,通常需要等待文件的打开、读取或写入操作完成。如果使用同步调用,程序会阻塞等待操作完成,影响程序的响应性和并发性。使用异步调用,可以在等待文件操作的同时执行其他任务,从而提高程序的效率和性能。
- 数据库查询:在进行数据库查询时,通常需要等待查询结果返回。如果使用同步调用,程序会阻塞等待查询结果,影响程序的响应性和并发性。使用异步调用,可以在等待查询结果的同时执行其他任务,从而提高程序的效率和性能。
Springboot实现异步
通过在方法上面加上==@Async注解,表示该方法开实现了异步调用,在主启动类上面加上@EnableAsync==来开启异步功能
第一步:在主启动类上面加上@EnableAsync注解
@EnableAsync
@SpringBootApplication
public class AsyncDemoApplication {
public static void main(String[] args) {
SpringApplication.run(AsyncDemoApplication.class, args);
}
}
第二步:在对应想要使用异步的方法上面加上注解@Async
@Service
public class UserServiceImpl {
@Async
public void asyncMethod() {
System.out.println("当前线程名字为:" + Thread.currentThread().getName());
}
}
第三步:测试
@Resource
private UserServiceImpl userService;
@Test
void contextLoads() {
userService.asyncMethod();
//当前线程名字为:task-1
}
多次调用方法,查看线程名字,可以看到使用了多线程
@Test
void contextLoads() {
for(int i = 0; i <= 1000; i++) {
userService.asyncMethod();
// 当前线程名字为:task-2
// 当前线程名字为:task-2
// 当前线程名字为:task-2
// 当前线程名字为:task-2
// 当前线程名字为:task-2
// 当前线程名字为:task-1
// 当前线程名字为:task-4
// 当前线程名字为:task-4
// 当前线程名字为:task-4
// 当前线程名字为:task-5
// 当前线程名字为:task-6
// 当前线程名字为:task-7
// 当前线程名字为:task-6
}
}
可以看到我们的线程数量增多以及cpu的利用率增高
自定义线程池代替默认线程池
自定义线程池可以根据实际需求和场景来优化线程池的行为,从而更好地满足应用程序的性能和资源管理需求。
主要有以下三个方面:
- 线程数量控制:默认线程池通常使用固定数量的线程,但在某些情况下,这可能不是最优的选择。自定义线程池可以根据实际需求动态地调整线程数量,以达到最佳的性能和资源利用率。
- 任务队列管理:默认线程池通常使用无界队列或有界队列来管理待执行的任务。然而,这可能会导致内存溢出或任务积压的问题。自定义线程池可以根据实际需求选择合适的任务队列类型,并设置合理的队列大小或拒绝策略,以避免资源耗尽或任务丢失的情况。
- 线程池参数调优:默认线程池的参数(如核心线程数、最大线程数、线程空闲时间等)可能无法满足特定应用程序的需求。自定义线程池可以根据实际情况进行参数调优,以提高线程池的性能和效率。例如,通过调整核心线程数和最大线程数的比例,可以在任务负载较轻时减少线程的创建和销毁开销。
==总之:==自定义线程池可以根据实际需求和场景来优化线程池的行为,以提高应用程序的性能、资源利用率和可管理性。通过灵活配置线程池的参数、任务队列和监控机制,可以更好地满足特定应用程序的需求,并提供更好的用户体验。
自定义线程池:
/**
* 定义异步任务执行线程池
* */
@Configuration
public class TaskPoolConfig {
@Bean("taskExecutor")
public Executor taskExecutor(){
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
// 核心线程数10:线程池创建时候初始化的线程数
executor.setCorePoolSize(10);
// 最大线程数20:线程池最大的线程数,只有在缓冲队列满了之后才会申请超过核心线程数的线程
executor.setMaxPoolSize(15);
// 缓冲队列200:用来缓冲执行任务的队列
executor.setQueueCapacity(200);
// 允许线程的空闲时间60秒:当超过了核心线程数之外的线程在空闲时间到达之后会被销毁
executor.setKeepAliveSeconds(60);
// 线程池名的前缀:设置好了之后可以方便定位处理任务所在的线程池
executor.setThreadNamePrefix("taskExecutor-");
/*
线程池对拒绝任务的处理策略:这里采用了CallerRunsPolicy策略,
当线程池没有处理能力的时候,该策略会直接在 execute 方法的调用线程中运行被拒绝的任务;
如果执行程序已关闭,则会丢弃该任务
*/
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
// 设置线程池关闭的时候等待所有任务都完成再继续销毁其他的Bean
executor.setWaitForTasksToCompleteOnShutdown(true);
// 设置线程池中任务的等待时间,如果超过这个时候还没有销毁就强制销毁,以确保应用最后能够被关闭,而不是阻塞住。
executor.setAwaitTerminationSeconds(60);
return executor;
}
}
测试:
@Async("taskExecutor")
public void asyncMethod() throws InterruptedException {
long start = System.currentTimeMillis();
for (int i = 0; i < 100000000; i++) {
}
Thread.sleep(2000);
long end = System.currentTimeMillis();
System.out.println("线程 " + Thread.currentThread().getName() + "\t耗时:" + (end - start));
}
@Test
void contextLoads() throws InterruptedException {
userService.asyncMethod();
//线程 taskExecutor-1 耗时:2017
}
}
```java
@Test
void contextLoads() throws InterruptedException {
userService.asyncMethod();
//线程 taskExecutor-1 耗时:2017
}
感谢观看,如有不对,还请多多指正(抱拳)。