在Spring Boot中使用@Async异步任务的线程池
在读这篇文章之前,我们先回答一个问题,什么是并发,并发和多线程是什么关系?
并发是指系统中存在多个独立的活动(任务、线程等),这些活动在一段时间内交替执行,从而使得多个活动在重叠的时间段内存在。在计算机科学中,通常指的是同时处理多个任务的能力。这些任务可能是同时运行的独立进程、线程或通过时间片轮转的方式交替执行。
多线程是实现并发的一种方式。在一个进程中,可以创建多个线程,每个线程执行一个独立的任务。这些线程共享相同的进程资源,如内存空间,但每个线程有自己的程序计数器、栈和局部变量。多线程使得程序能够同时执行多个任务,提高了系统的资源利用率和响应速度。
并发和多线程之间存在密切关系。多线程是一种实现并发的方式,通过同时运行多个线程,可以在同一时间内执行多个任务,从而实现并发。在并发系统中,多线程可以同时处理多个任务,使得系统更加高效、灵活和响应性强。然而,要确保多线程的正确执行,需要注意线程之间的同步、互斥和共享资源的管理,以避免潜在的竞态条件和数据一致性问题。
那么我们来说说,既然有了@Async,我们还需要使用他提供的线程池?
虽然使用@Async
注解允许方法异步执行,但它并不提供底层的线程池管理。默认情况下,Spring会使用一个简单的任务执行器来执行异步方法,这可能不是在所有场景下都是理想的。
- 自定义线程池配置:使用
@Async
注解时候,默认情况相爱,我们Spring 使用的是SimpleAsyncTaskExecutor
,这个执行器每次都会创建一个新的线程来执行任务,这也可能并非在我们所有的情况下都是最佳的选择,我们可以通过@Async
提供的线程池,可以通过配置线程池的大小,队列容量,线程命名等,可以满足我们的应用程序的需求。 - 线程池的管理和监控: @Async提供的线程池是一个ThreadPoolTaskExecutor,它是TaskExecutor接口的实现。这使得可以更容易地与Spring的其他任务调度和执行功能进行集成,例如与@Scheduled注解一起使用。此外,Spring提供了对线程池的监控和管理的支持,你可以在Spring的JMX(Java Management Extensions)中监视和管理线程池的运行状况。
- 任务拒绝策略: 通过使用@Async提供的线程池,你可以配置任务拒绝策略来处理在线程池已满时提交的任务。这允许你在高负载情况下更好地控制任务的流量,防止系统过载。
- 更好的性能和资源利用: 通过使用合适配置的线程池,可以更好地控制线程的数量,防止系统过度消耗资源。线程池的管理机制可以有效地重用线程,减少线程创建和销毁的开销。
接下来我们来看一下一个案例:
配置一个线程池,以控制异步任务的执行。在你的Spring Boot应用程序的配置类中,添加@Bean
注解的方法,返回TaskExecutor
类型的bean。
@Configuration
@EnableAsync
public class AsyncConfig {
@Bean(name = "asyncExecutor")
public TaskExecutor asyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(5); // 设置核心线程池大小
executor.setMaxPoolSize(10); // 设置最大线程池大小
executor.setQueueCapacity(100); // 设置队列容量
executor.setThreadNamePrefix("AsyncThread-"); // 设置线程名前缀
executor.initialize();
return executor;
}
}
创建一个Service类:
@Service
public class AnotherService {
private final MyAsyncService myAsyncService;
@Autowired
public AnotherService(MyAsyncService myAsyncService) {
this.myAsyncService = myAsyncService;
}
public void performAsyncTask() {
myAsyncService.asyncMethod();
myAsyncService.asyncMethod();
myAsyncService.asyncMethod();
myAsyncService.asyncMethod();
myAsyncService.asyncMethod();
// 其他逻辑
}
}
在上边asyncExecutor
方法返回一个ThreadPoolTaskExecutor
实例。
服务类或组件中,使用@Async
注解标记异步方法
@Service
public class MyAsyncService {
@Async
public void asyncMethod() {
// 异步任务的实现
System.out.println("Async method executed by thread: " + Thread.currentThread().getName());
}
}
之后我们创建一个单元测试来测试一下:
@Slf4j
@SpringBootTest
public class ApplicationTests {
@Autowired
private AnotherService anotherService;
@Test
public void test1() throws Exception {
anotherService.performAsyncTask();
}
}
看一下Anysc的相关参数
有关于参数,我这里进行详细介绍一下:
private final Object poolSizeMonitor = new Object();
private int corePoolSize = 1;
private int maxPoolSize = Integer.MAX_VALUE;
private int keepAliveSeconds = 60;
private int queueCapacity = Integer.MAX_VALUE;
private boolean allowCoreThreadTimeOut = false;
@Nullable
private TaskDecorator taskDecorator;
@Nullable
private ThreadPoolExecutor threadPoolExecutor;
private final Map<Runnable, Object> decoratedTaskMap;
poolSizeMonitor:
类型:Object
说明:这是一个监视线程池大小的对象。在代码中,使用 synchronized 块来确保对线程池大小的修改是线程安全的。
corePoolSize:
类型:int
默认值:1
说明:核心线程池大小,即线程池中保持存活的线程数量。这些线程在没有任务执行时仍然存活,减少了线程的创建和销毁开销。
maxPoolSize:
类型:int
默认值:Integer.MAX_VALUE
说明:线程池中允许的最大线程数。当核心线程都在忙于执行任务且队列已满时,新任务将创建额外线程,但数量不超过 maxPoolSize。
keepAliveSeconds:
类型:int
默认值:60
说明:非核心线程的闲置时间超过此值时,线程将被终止。这有助于控制线程池的大小,避免过多的线程资源占用。
queueCapacity:
类型:int
默认值:Integer.MAX_VALUE
说明:任务队列的容量,用于保存等待执行的任务。当所有核心线程都在忙于执行任务时,新任务将被放入队列中等待。
allowCoreThreadTimeOut:
类型:boolean
默认值:false
说明:确定核心线程是否也可以因为闲置超时而被终止。如果设置为true,即使核心线程在执行任务之后处于空闲状态,它们也可能被终止。
taskDecorator:
类型:TaskDecorator
默认值:null
说明:任务装饰器,用于在将任务提交到线程池之前修改任务的行为。可以用于记录任务执行时间、添加上下文信息等。
threadPoolExecutor:
类型:ThreadPoolExecutor
说明:实际的 ThreadPoolExecutor 实例。在运行时,这个字段可能会被初始化为ThreadPoolExecutor 的实例,用于执行异步任务。
decoratedTaskMap:
类型:Map<Runnable, Object>
说明:装饰过的任务的映射。这可能用于跟踪已提交的任务及其状态。
我们可以利用以上的参数配置我们的@Async异步参数。根据你自己的情况进行选择。