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(): 一直用最新的任务,代替队列中最老的任务

 

posted on 2020-04-20 14:25  dreamstar  阅读(7338)  评论(0)    收藏  举报