SpringBoot线程池的创建、@Async配置步骤及注意事项

本文介绍如何在Spring Boot中使用@Async注解实现异步任务处理,包括配置自定义线程池、避免常见错误以及展示实际运行效果。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前  言:
最近在做订单模块,用户购买服务类产品之后,需要进行预约,预约成功之后分别给商家和用户发送提醒短信。考虑发短信耗时的情况所以我想用异步的方法去执行,于是就在网上看见了Spring的@Async了。但是遇到了许多问题,使得@Async无效,也一直没有找到很好的文章去详细的说明@Async的正确及错误的使用方法及需要注意的地方,这里简单整理了一下遇见的问题,Spring是以配置文件的形式来开启@Async,而SpringBoot则是以注解的方式开启。

ps:线程池配合定时任务效果绝佳:

基于springBoot的定时任务https://blog.youkuaiyun.com/Muscleheng/article/details/106838086

我们可以使用springBoot默认的线程池,不过一般我们会自定义线程池(因为比较灵活),配置方式有:

1. 使用 xml 文件配置的方式

2. 使用Java代码结合@Configuration进行配置(推荐使用

下面分别实现两种配置方式

第一步、配置@Async

一、springBoot启动类的配置:

在Spring Boot的主程序中配置@EnableAsync,如下所示:

@ServletComponentScan
@SpringBootApplication
@EnableAsync // 开启@Async注解
public class DemoApplication {
	public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }
}

二、Spring XML的配置方式:

1. applicationContext.xml同目录下创建文件threadPool.xml文件:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:task="http://www.springframework.org/schema/task"
	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
                        http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task.xsd">
                        
	<!-- 开启异步,并引入线程池 -->
	<task:annotation-driven executor="threadPool" />
	
	<!-- 定义线程池 -->
	<bean id="threadPool"
		class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor">
		<!-- 核心线程数,默认为1 -->
		<property name="corePoolSize" value="10" />
 
		<!-- 最大线程数,默认为Integer.MAX_VALUE -->
		<property name="maxPoolSize" value="50" />
 
		<!-- 队列最大长度,一般需要设置值>=notifyScheduledMainExecutor.maxNum;默认为Integer.MAX_VALUE -->
		<property name="queueCapacity" value="100" />
 
		<!-- 线程池维护线程所允许的空闲时间,默认为60s -->
		<property name="keepAliveSeconds" value="30" />
 
		<!-- 完成任务自动关闭 , 默认为false-->
		<property name="waitForTasksToCompleteOnShutdown" value="true" />
 
		<!-- 核心线程超时退出,默认为false -->
		<property name="allowCoreThreadTimeOut" value="true" />
 
		<!-- 线程池对拒绝任务(无线程可用)的处理策略,目前只支持AbortPolicy、CallerRunsPolicy;默认为后者 -->
		<property name="rejectedExecutionHandler">
			<!-- AbortPolicy:直接抛出java.util.concurrent.RejectedExecutionException异常 -->
			<!-- CallerRunsPolicy:主线程直接执行该任务,执行完之后尝试添加下一个任务到线程池中,可以有效降低向线程池内添加任务的速度 -->
			<!-- DiscardOldestPolicy:抛弃旧的任务、暂不支持;会导致被丢弃的任务无法再次被执行 -->
			<!-- DiscardPolicy:抛弃当前任务、暂不支持;会导致被丢弃的任务无法再次被执行 -->
			<bean class="java.util.concurrent.ThreadPoolExecutor$CallerRunsPolicy" />
		</property>
	</bean>
</beans>

2. 然后在applicationContext.xml中引入threadPool.xml:<import resource="threadPool.xml" />

<!--如果不使用自定义线程池,可以直接使用下面这段标签-->
<!-- 
<task:executor id="WhifExecutor" pool-size="10"/> 
-->
<import resource="threadPool.xml" />
<task:annotation-driven executor="WhifExecutor" />

第二步:创建两个异步方法的类,如下所示:

第一个类(这里模拟取消订单后发短信,有两个发送短信的方法):

@Service
public class TranTest2Service {
	

	// 发送提醒短信 1
	@Async
	public void sendMessage1() throws InterruptedException {
		
		System.out.println("发送短信方法---- 1   执行开始");
		Thread.sleep(5000); // 模拟耗时
		System.out.println("发送短信方法---- 1   执行结束");
	}
	
	// 发送提醒短信 2
	@Async
	public void sendMessage2() throws InterruptedException {
		
		System.out.println("发送短信方法---- 2   执行开始");
		Thread.sleep(2000); // 模拟耗时
		System.out.println("发送短信方法---- 2   执行结束");
	}
}

第二个类。调用发短信的方法 (异步方法不能与被调用的异步方法在同一个类中,否则无效):

@Service
public class OrderTaskServic {
	@Autowired
	private TranTest2Service tranTest2Service;
	
	// 订单处理任务
	public void orderTask() throws InterruptedException {
		
		this.cancelOrder(); // 取消订单
		tranTest2Service.sendMessage1(); // 发短信的方法   1
		tranTest2Service.sendMessage2(); // 发短信的方法  2
		
	}

	// 取消订单
	public void cancelOrder() throws InterruptedException {
		System.out.println("取消订单的方法执行------开始");
		System.out.println("取消订单的方法执行------结束 ");
	}
	
}

经过测试得到如下结果:

1. 没有使用@Async

2. 使用了@Async

可以看出,没有使用@Async方式实现的发送短信是同步执行的,意思就是说第一条发送之后再发送第二条,第二条发送成功之后再给用户提示,这样显然会影响用户体验,再看使用了@Async实现的,在执行第一个发送短信方法之后马上开启另一个线程执行第二个方法,显然这样我们的处理速度回快很多。

使用Java代码结合@Configuration注解的配置方式(推荐使用

1. 新建一个配置类

package com.boot.common.conf;

import java.util.concurrent.ThreadPoolExecutor;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

/**
 * 线程池配置
 * @author zhh
 *
 */
@Configuration
@EnableAsync
public class ThreadPoolTaskConfig {

/** 
 *   默认情况下,在创建了线程池后,线程池中的线程数为0,当有任务来之后,就会创建一个线程去执行任务,
 *	当线程池中的线程数目达到corePoolSize后,就会把到达的任务放到缓存队列当中;
 *  当队列满了,就继续创建线程,当线程数量大于等于maxPoolSize后,开始使用拒绝策略拒绝 
 */
	
	/** 核心线程数(默认线程数) */
	private static final int corePoolSize = 20;
	/** 最大线程数 */
	private static final int maxPoolSize = 100;
	/** 允许线程空闲时间(单位:默认为秒) */
	private static final int keepAliveTime = 10;
	/** 缓冲队列大小 */
	private static final int queueCapacity = 200;
	/** 线程池名前缀 */
	private static final String threadNamePrefix = "Async-Service-";
	
	@Bean("taskExecutor") // bean的名称,默认为首字母小写的方法名
	public ThreadPoolTaskExecutor taskExecutor(){
		ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
		executor.setCorePoolSize(corePoolSize);   
		executor.setMaxPoolSize(maxPoolSize);
		executor.setQueueCapacity(queueCapacity);
		executor.setKeepAliveSeconds(keepAliveTime);
		executor.setThreadNamePrefix(threadNamePrefix);
		
		// 线程池对拒绝任务的处理策略
        // CallerRunsPolicy:由调用线程(提交任务的线程)处理该任务
		executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
		// 初始化
		executor.initialize();
		return executor;
	}
}

2:创建两个异步方法的类(和之前的类类似仅仅是方法上注解不一样),如下所示:

第一个类(这里模拟取消订单后发短信,有两个发送短信的方法):

package com.boot.test1.service;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

@Service
public class TranTest2Service {
	Logger log = LoggerFactory.getLogger(TranTest2Service.class);
 
	// 发送提醒短信 1
	@Async("taskExecutor")// 指定线程池,也可以直接写@Async
	public void sendMessage1() throws InterruptedException {
		log.info("发送短信方法---- 1   执行开始");
		Thread.sleep(5000); // 模拟耗时
		log.info("发送短信方法---- 1   执行结束");
	}
	
	// 发送提醒短信 2
	@Async("taskExecutor") // 指定线程池,也可以直接写@Async
	public void sendMessage2() throws InterruptedException {
		
		log.info("发送短信方法---- 2   执行开始");
		Thread.sleep(2000); // 模拟耗时
		log.info("发送短信方法---- 2   执行结束");
	}
}

代码中的 @Async("taskExecutor") 对应我们自定义线程池中的 @Bean("taskExecutor") ,表示使用我们自定义的线程池。也可以直接写@Async

第二个类。调用发短信的方法 (异步方法不能与被调用的异步方法在同一个类中,否则无效):

@Service
public class OrderTaskServic {
	@Autowired
	private TranTest2Service tranTest2Service;
	
	// 订单处理任务
	public void orderTask() throws InterruptedException {
		
		this.cancelOrder(); // 取消订单
		tranTest2Service.sendMessage1(); // 发短信的方法   1
		tranTest2Service.sendMessage2(); // 发短信的方法  2
		
	}

	// 取消订单
	public void cancelOrder() throws InterruptedException {
		System.out.println("取消订单的方法执行------开始");
		System.out.println("取消订单的方法执行------结束 ");
	}
	
}

运行截图:

注意看,截图中的 [nio-8090-exec-1] 是Tomcat的线程名称

 [Async-Service-1]、[Async-Service-2]表示线程1和线程2 ,是我们自定义的线程池里面的线程名称,我们在配置类里面定义的线程池前缀:

private static final String threadNamePrefix = "Async-Service-"; // 线程池名前缀,说明我们自定义的线程池被使用了。

注意事项:

如下方式会使@Async失效


一、异步方法使用static修饰
二、异步类没有使用@Component注解(或其他注解)导致spring无法扫描到异步类
三、异步方法不能与被调用的异步方法在同一个类中
四、类中需要使用@Autowired或@Resource等注解自动注入,不能自己手动new对象
五、如果使用SpringBoot框架必须在启动类中增加@EnableAsync注解

 

 

<think>好的,我现在需要解决用户的问题:在Spring Boot项目中,使用@Async注解的方法是否使用了线程池,以及如何设置线程池的参数。这个问题看起来属于Spring框架的异步处理机制,特别是关于线程池配置部分。 首先,用户可能已经知道@Async的基本用法,但想深入了解背后的线程管理机制。我需要确认@Async默认是否使用线程池,如果是的话,是什么类型的线程池。接下来,用户可能想知道如何自定义这个线程池的参数,比如核心线程数、最大线程数、队列容量等。 我需要回忆Spring Boot的文档和相关知识。记得在Spring中,当启用@EnableAsync时,默认会使用SimpleAsyncTaskExecutor,但这个执行器实际上不是线程池,而是为每个任务新建一个线程。这可能不是用户所预期的,特别是如果用户期望重用线程或者控制并发量的话。不过,可能Spring Boot在自动配置时做了不同的处理,或者用户需要显式配置线程池。 接下来,用户想知道如何设置线程池参数。这时候需要说明如何通过配置类自定义ThreadPoolTaskExecutor,并指定@Async使用的执行器。可能需要提到使用@Bean注解创建TaskExecutor,并在@Async中指定bean的名称。此外,还可以通过application.properties或application.yml文件来配置线程池参数,但需要确认是否Spring Boot提供了默认的属性支持,或者是否需要自定义配置类。 另外,用户可能关心默认配置是否适合生产环境,比如默认的线程池参数是否会导致资源耗尽。需要建议用户根据实际需求调整参数,比如核心线程数、最大线程数、队列容量以及拒绝策略等。 还需要注意,可能存在多个异步方法需要使用不同的线程池,这时候如何通过指定不同的执行器名称来实现。例如,在@Async("customExecutor")中指定不同的bean名称,从而使用不同的线程池配置。 另外,可能涉及到异常处理的问题,比如异步方法中的异常如何捕获,但用户的问题主要关注线程池的设置,这部分可能暂时不需要深入,除非用户后续有相关问题。 还需要检查是否有常见的错误配置,比如忘记在启动类上添加@EnableAsync,或者配置线程池但没有正确引用。需要提醒用户确保这些步骤正确。 总结一下,回答的结构应该是: 1. 确认@Async默认是否使用线程池,解释默认行为。 2. 如何自定义线程池参数,包括通过配置类和使用配置文件。 3. 可能的高级配置,比如多个线程池、拒绝策略等。 4. 注意事项和常见问题。 需要确保回答准确,并且步骤清晰,帮助用户逐步配置。同时,使用代码示例来说明配置方法,比如定义一个配置类,使用@Bean创建TaskExecutor,然后在@Async中指定名称。同时,提到可以通过属性文件配置参数,但可能需要结合Java配置实现,因为直接通过属性文件可能不够灵活,或者需要Spring Boot的特定属性支持。 另外,需要验证是否在Spring Boot中,当添加spring-boot-starter-web依赖后,是否自动配置ThreadPoolTaskExecutor,或者是否需要手动配置。可能默认情况下,如果没有自定义的TaskExecutor,@Async会使用SimpleAsyncTaskExecutor,但用户通常需要自定义线程池以更好地管理资源。 最后,确保回答符合用户的需求,即设置线程池参数的方法,并指出常见的配置项及其作用,帮助用户根据实际应用场景调整这些参数。</think>在 Spring Boot 项目中,使用 `@Async` 注解的方法默认会使用线程池执行异步任务,但其默认配置可能不符合生产需求。以下是详细配置方法和注意事项: --- ### 一、默认线程池行为 1. **默认使用的线程池** - 若未显式配置线程池Spring Boot 会使用 `SimpleAsyncTaskExecutor`,但**严格来说这并不是线程池**。 - `SimpleAsyncTaskExecutor` 会为每个异步任务创建线程,没有线程复用机制,可能导致资源耗尽。 2. **Spring Boot 自动配置线程池** - 如果项目中引入了 `spring-boot-starter-web`,Spring Boot 会默认配置 `ThreadPoolTaskExecutor`,但需要手动启用。 --- ### 二、显式配置线程池 #### 1. **通过配置类自定义线程池** ```java @Configuration @EnableAsync // 启用异步支持 public class AsyncConfig { @Bean("taskExecutor") // 定义线程池 Bean 名称 public Executor taskExecutor() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); // 核心线程数(默认值通常为 CPU 核心数) executor.setCorePoolSize(5); // 最大线程数(根据任务类型调整) executor.setMaxPoolSize(10); // 任务队列容量(避免直接拒绝任务) executor.setQueueCapacity(100); // 线程名前缀(方便日志追踪) executor.setThreadNamePrefix("Async-Thread-"); // 拒绝策略(默认抛出异常) executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); // 初始化线程池 executor.initialize(); return executor; } } ``` #### 2. **在 `@Async` 中指定线程池** ```java @Service public class MyService { @Async("taskExecutor") // 使用自定义线程池 public void asyncMethod() { // 异步任务逻辑 } } ``` --- ### 三、通过配置文件调整参数(可选) 在 `application.properties` 或 `application.yml` 中直接配置线程池参数: ```properties # application.properties spring.task.execution.pool.core-size=5 spring.task.execution.pool.max-size=10 spring.task.execution.pool.queue-capacity=100 spring.task.execution.thread-name-prefix=Async-Thread- ``` > **注意**:这种方式需要 Spring Boot 2.1+ 版本,且配置线程池会被自动注入到 `@Async` 方法中。 --- ### 四、线程池参数调优建议 1. **核心参数** - **`corePoolSize`**:建议设置为 CPU 核心数(如 4C 机器设为 4)。 - **`maxPoolSize`**:根据任务类型调整(IO 密集型可设高,CPU 密集型建议与核心数接近)。 - **`queueCapacity`**:避免过大导致内存溢出,过小导致频繁拒绝任务。 2. **拒绝策略选择** - `AbortPolicy`(默认):直接抛出异常。 - `CallerRunsPolicy`:由调用方线程执行任务。 - `DiscardPolicy`:静默丢弃任务。 - `DiscardOldestPolicy`:丢弃队列中最旧的任务。 --- ### 五、注意事项 1. **避免默认线程池的坑** - 不配置线程池直接使用 `@Async` 会导致无限制创建线程(`SimpleAsyncTaskExecutor` 行为),需显式配置。 2. **线程池隔离** - 为不同类型任务配置不同线程池(如 `@Async("ioTaskExecutor")` 和 `@Async("cpuTaskExecutor")`),避免相互影响。 3. **异常处理** - 异步方法需自行捕获异常,或实现 `AsyncUncaughtExceptionHandler` 接口统一处理: ```java @Configuration public class AsyncExceptionConfig implements AsyncConfigurer { @Override public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() { return new CustomAsyncExceptionHandler(); } } ``` 4. **监控线程池状态** - 通过 `ThreadPoolTaskExecutor` 的 `getActiveCount()`、`getQueue().size()` 等方法监控运行状态,或集成 Micrometer 上报指标。 --- ### 六、验证配置是否生效 通过日志观察线程名称是否符合配置的命名规则: ```java @Async("taskExecutor") public void logThreadName() { log.info("当前线程: {}", Thread.currentThread().getName()); // 输出示例: Async-Thread-1 } ``` --- ### 总结 在 Spring Boot 中,`@Async` 方法默认依赖线程池执行任务,但需显式配置 `ThreadPoolTaskExecutor` 以优化资源使用。通过调整核心线程数、队列容量和拒绝策略,可显著提升异步任务性能。建议结合具体业务场景进行压测调优。
评论 18
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值