Springboot 多线程异步
在springBoot项目中,可以使用@EnableAsync 和 @Async 来实现多线程异步执行任务。
Spring中使用异步多线程的步骤
1.使用@EnableAsync注解开启多线程
2.自定义线程池或使用默认线程池,推荐自定义线程池
3.在需要并发执行的public方法上使用@Async注解
场景1:应用中只有一个线程池和一个异步方法。那么这个异步方法从唯一线程池中获取可用线程
1.创建一个配置类,自定义异步任务的线程池并启用异步功能
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.Executor; @Configuration @EnableAsync public class ThreadPoolConfig { //创建一个名为taskExecutorA的线程池 @Bean public Executor taskExecutorA() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); // 核心线程数 executor.setCorePoolSize(20); // 最大线程数 executor.setMaxPoolSize(50); // 队列容量 executor.setQueueCapacity(100); // 线程存活时间(秒) executor.setKeepAliveSeconds(60); // 线程名称前缀 executor.setThreadNamePrefix("AsyncExecutor-"); // 拒绝策略:抛出异常 executor.setRejectedExecutionHandler(new java.util.concurrent.ThreadPoolExecutor.AbortPolicy()); // 初始化线程池 executor.initialize(); return executor; } }
2.定义异步方法,在方法上使用注解@Async声明该方法是异步
import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Service; @Service public class AsyncClassA { private static final Logger logger = LoggerFactory.getLogger(AsyncClassA.class); @Async public void asyncMethodA(int i) throws InterruptedException { logger.info("methondA started {}",i); Thread.sleep(10000); logger.info("methondA completed {}",i); } }
3.调用异步方法
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping("/api") public class AsyncController { @Autowired private AsyncClassA asyncClassA; @GetMapping("/testAsync") public void testAsync() throws InterruptedException { for (int i = 0; i < 10; i++) { asyncClassA.asyncMethodA(i); } } }
4.测试,启动应用,访问http://localhost:8080/api/testAsync,查看log
有10个任务任务需要同时执行,那么会开5核心线程,5个在队列等待,等到前5任务执行成功释放线程,再继续执行队列里的任务。
场景2:应用中只有一个线程池和多个异步方法。那么所有异步方法都从这唯一线程池中获取可用线程
2.1 修改AsyncClassA如下,再定义一个异步方法asyncMethodB
import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Service; @Service public class AsyncClassA { private static final Logger logger = LoggerFactory.getLogger(AsyncClassA.class); @Async public void asyncMethodA(int i) throws InterruptedException { logger.info("methondA started {}",i); Thread.sleep(10000); logger.info("methondA completed {}",i); } @Async public void asyncMethodB(int i) throws InterruptedException { logger.info("methondB started {}",i); Thread.sleep(10000); logger.info("methondB completed {}",i); } }
2.2 修改AsyncController如下
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping("/api") public class AsyncController { @Autowired private AsyncClassA asyncClassA; @GetMapping("/testAsync") public void testAsync() throws InterruptedException { for (int i = 0; i < 10; i++) { asyncClassA.asyncMethodA(i); asyncClassA.asyncMethodB(i); } } }
2.3 再次启动应用,访问http://localhost:8080/api/testAsync,查看log
场景3:应用中有多个线程池+多个异步方法,不同的异步方法需要从不同的线程池中获取线程,那么可以定义多个线程池,在异步方法上指定线程池的名字
3.1 修改ThreadPoolConfig如下,创建两个线程池
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.Executor; @Configuration @EnableAsync public class ThreadPoolConfig { //创建一个名为taskExecutorA的线程池 @Bean public Executor taskExecutorA() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); // 核心线程数 executor.setCorePoolSize(5); // 最大线程数 executor.setMaxPoolSize(50); // 队列容量 executor.setQueueCapacity(100); // 线程存活时间(秒) executor.setKeepAliveSeconds(60); // 线程名称前缀 executor.setThreadNamePrefix("AsyncExecutor-A-"); // 拒绝策略:抛出异常 executor.setRejectedExecutionHandler(new java.util.concurrent.ThreadPoolExecutor.AbortPolicy()); // 初始化线程池 executor.initialize(); return executor; } //创建一个名为taskExecutorB的线程池 @Bean public Executor taskExecutorB() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); // 核心线程数 executor.setCorePoolSize(5); // 最大线程数 executor.setMaxPoolSize(50); // 队列容量 executor.setQueueCapacity(100); // 线程存活时间(秒) executor.setKeepAliveSeconds(60); // 线程名称前缀 executor.setThreadNamePrefix("AsyncExecutor-B-"); // 拒绝策略:抛出异常 executor.setRejectedExecutionHandler(new java.util.concurrent.ThreadPoolExecutor.AbortPolicy()); // 初始化线程池 executor.initialize(); return executor; } }
3.2 修改AsyncClassA如下, methodA从taskExecutorA 获取线程, methodB从taskExecutorB池中获取线程
import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Service; @Service public class AsyncClassA { private static final Logger logger = LoggerFactory.getLogger(AsyncClassA.class); @Async("taskExecutorA") public void asyncMethodA(int i) throws InterruptedException { logger.info("methondA started {}",i); Thread.sleep(10000); logger.info("methondA completed {}",i); } @Async("taskExecutorB") public void asyncMethodB(int i) throws InterruptedException { logger.info("methondB started {}",i); Thread.sleep(10000); logger.info("methondB completed {}",i); } }
3.3 再次启动应用,访问http://localhost:8080/api/testAsync,查看log
注意: 异步方法和调用异步方法的方法要在不同的类中,同一个类中,异步方法会不生效。
线程池属性解释和线程执行顺序
核心线程数:
线程池中保持的线程数量。
即使这些线程处于空闲状态,它们也会一直存在于线程池中,以备接收新的任务。
当线程池中的线程数小于核心线程数,新的任务将会创建新的线程来执行。
最大线程数:
线程池中允许的最大线程数量。
当线程池中的线程数量达到核心线程数+并且队列中待执行任务以达到最大容量,如果还有新的任务需要执行,线程池将会创建新的线程,直到达到最大线程数。
当线程池中的线程数量达到最大值并且队列也已满时,则会拒绝执行新的任务。
队列容量
线程池中任务队列可以容纳的最大任务量。
当线程池中的线程数量达到最大线程数时,如果还有新的任务要执行,线程池会将这些任务添加到任务队列。
执行顺序: 核心线程->队列->最大线程数
比如核心线程2,最大线程4,队列5
任务数<=2,就开2个线程
2<任务数<=7(核心线程+队列容量): 2个核心线程 + <=5个在队列等待
7<任务数<=9(最大线程+队列容量),比如8个任务:就2个核心线程 + 4个在队列等待 + 2个线程
9<任务数:当请求数量> 队列容量 + 最大线程数,就会报错
线程活跃时间
当线程空闲时间大于设置的时间时,线程会被回收,知道线程池中的线程数量等于核心线程数。
如果设置了,那么核心线程空闲时间大于设置时间时,也会退出
拒绝策略
当等待执行的任务>队列容量 + 最大线程数 时,就会被Spring拒绝接收,有如下几种拒绝策略
AbortPolicy(): 默认策略,如果线程池队列已满,丢掉后进来的线程任务,不让他们进入队列,同时抛出异常
DiscardPolicy():如果线程池队列已满,丢掉后进来的线程任务,不让他们进入队列,但是不会抛出异常
iscardOldestPolicy(): 一直用最新的任务,代替队列中最老的任务
浙公网安备 33010602011771号