SpringBoot线程池ThreadPoolTaskExecutor和@Async异步方法浅解及代码应用示例

目录

线程池执行顺序

线程池配置策略

Spring线程池的配置类:

Spring有、无返回值的异步调用示例

自定义的异步方法类代码:

测试类代码

常见问题:

参考文章:


线程池执行顺序

核心线程数(CorePoolSize) ---> 全部被占用 ----> 剩余线程任务放入阻塞队列 ----> 队列全部被占用 ----> 根据最大线程数(MaxPoolSize)扩充线程数量 ----> 全部线程和队列都被占用 ----> 执行拒绝策略(舍弃并报错,舍弃但不报错,丢给调用方的线程执行等。。)

线程池配置策略

首先要了解自己本机或服务器的物理核心数,线程数设置太大会导致线程不停地切换,并不是真正意义上的并行。

(1)高并发、任务执行时间短的业务,线程池线程数可以设置为CPU核数+1,减少线程上下文的切换,可以将阻塞队列设置大一点,最大线程数与核心线程数相当,这样可以避免创建太多的线程。 (2)并发不高、任务执行时间长的业务要区分开看: a、假如是业务时间长集中在IO操作上,也就是IO密集型的任务,因为IO操作并不占用CPU,所以不要让CPU闲下来,可以加大线程池中的线程数目,让CPU处理更多的业务 b、假如是业务时间长集中在计算操作上,也就是计算密集型任务,这个就没办法了,和 (1) 一样吧,线程池中的线程数设置得少一些,减少线程上下文的切换

Spring线程池的配置类:

举例了两种不同的线程池策略,在注入时需要标明bean的name属性,即可调用具体的线程池。

注:不同业务不要混用同一种线程池策略,应该单独配置适合业务的配置,

        同一台服务器中的微服务的线程池总数不能太多,

        最大线程数必须大于等于核心线程数,否则会报错。

package com.example.jucdemo.config;

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.ThreadPoolExecutor;

@Configuration
@EnableAsync
public class ThreadPoolTaskConfig {
    // 低并发,IO耗时长的策略
    @Bean(name = "myExecutor")
    public ThreadPoolTaskExecutor taskExecutor(){
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        // 较大核心线程数
        executor.setCorePoolSize(20);
        executor.setMaxPoolSize(100);
        executor.setKeepAliveSeconds(60);
        executor.setQueueCapacity(30);
        // 设置线程池内线程名称的前缀
        executor.setThreadNamePrefix("IO-threadPool-");
        //设置任务的拒绝策略
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy());
        //初始化
        executor.initialize();
        return executor;
    }

    // 高并发,耗时短的策略
    @Bean(name = "concurrencyExecutor")
    public ThreadPoolTaskExecutor concurrencyExecutor(){
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        // 小核心线程数
        executor.setCorePoolSize(5);
        executor.setMaxPoolSize(5);
        executor.setKeepAliveSeconds(2);
        // 大阻塞队列
        executor.setQueueCapacity(500);
        // 设置线程池内线程名称的前缀
        executor.setThreadNamePrefix("concurrency-threadPool-");
        // 设置任务的拒绝策略
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.DiscardPolicy());
        // 初始化
        executor.initialize();
        return executor;
    }
}

Spring有、无返回值的异步调用示例

无返回值的较为简单,直接把耗时的任务设置为异步方法,丢给线程池执行。

有返回值的需要异步方法返回的是 Future<> 接口,

一般return new AsyncResult<>(result),将真正的结果传入。AsyncResult实现了ListenableFuture接口,而后者又继承了Future接口。

在需要其结果时调用 future.get()方法即可将结果取出,get()方法会阻塞当前线程,等待异步方法的返回值。

直接用 threadPoolTaskExecutor.excute(Runnable task)或.submit(Runnable task)来执行无返回值的异步方法。

用 .submit(Callable task) 方法执行有返回值的异步方法。这里的Runnable和Callable都是函数式接口,只有一个抽象方法,因此建议使用lamda表达式写法更为简洁。

具体见下面的代码示例。

自定义的异步方法类代码:

其中的 @Async 注解可在类或方法上标注,分别用来标记一个类下的所有方法为异步方法,或标记一个具体的方法为异步方法,可传入具体使用的线程池的bean的名字。

package com.example.jucdemo.service;

import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.AsyncResult;
import org.springframework.stereotype.Service;

import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;

@Service
public class MyAsyncTest { 
    // 无返回值
    @Async("myExecutor")
    public void execute(){
        System.out.println("开始执行异步任务--1");
        try {
            for (int i=0;i<10;i++){
                System.out.println("异步任务--1:第"+(i+1)+"次");
                Thread.sleep(100);
            }
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println("异步任务--1:最终完成");
    }
    // 有返回值
    @Async("myExecutor")
    public Future<Integer> algorithm1(Integer var1,Integer var2) throws InterruptedException {
        Thread.sleep(1000);
        return new AsyncResult<>(var1+var2);
    }

    @Async("myExecutor")
    public Future<Integer> algorithm2(Integer var1,Integer var2) throws InterruptedException {
        Thread.sleep(2000);
        return new AsyncResult<>(var1 * var2);
    }

    @Async("myExecutor")
    public Future<String> circulation(int i) throws InterruptedException {
        Thread.sleep(500);
        return new AsyncResult<>(String.format("结果一:%s",i));
    }

}

测试类代码

package com.example.jucdemo;

import com.example.jucdemo.service.MyAsyncTest;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;

@SpringBootTest
class JucDemoApplicationTests {
    @Resource(name = "myExecutor")
    ThreadPoolTaskExecutor executor;

    @Resource
    MyAsyncTest asyncTest;

    // 无返回值的异步调用
    @Test
    public void executeTest() throws InterruptedException {

        System.out.println("主线程开始执行");
        // 方式一:调用 MyAsyncTest 类中定义的异步方法
        asyncTest.execute();

        // 方式二:直接调用线程池的execute方法
        executor.execute(() -> {
            System.out.println("开始执行异步任务--2");
            for (int i=0;i<10;i++){
                try {
                    System.out.println("异步任务--2:第"+(i+1)+"次");
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
            System.out.println("异步任务--2:最终完成");
        });

        //由于单元测试主线程结束进程直接结束,所以主线程需要sleep等待异步方法
        Thread.sleep(2000);
        System.out.println("进程结束");
    }

    // 带返回值的异步调用
    @Test
    public void submitTest() throws ExecutionException, InterruptedException {
        long start = System.currentTimeMillis();
        // 调用异步算法1
        Future<Integer> param1 = executor.submit(() -> {
            System.out.println("正在计算param1");
            Thread.sleep(3000);
            return 1;
        });
        // 调用异步算法2
        Future<Integer> param2 = executor.submit(() -> {
            System.out.println("正在计算param2");
            Thread.sleep(5000);
            return 2;
        });

        // 等待接收异步算法的结果
        int result = param1.get() + param2.get();
        long end = System.currentTimeMillis();
        long time = end - start;
        System.out.println("总共耗时:" + time);
        System.out.println("计算结果为:" + result);
    }

    // 带返回值的异步方法的调用
    @Test
    public void asyncResultTest() throws InterruptedException, ExecutionException {
        long start = System.currentTimeMillis();
        System.out.println("开始:");
        int var1 = 2;
        int var2 = 3;
        Future<Integer> result1 = asyncTest.algorithm1(var1, var2);
        Future<Integer> result2 = asyncTest.algorithm2(var1, var2);
        System.out.println("结果二" + result2.get());
        long end1 = System.currentTimeMillis();
        System.out.println("结果一:" + result1.get());
        long end2 = System.currentTimeMillis();
        System.out.println("时间一:" + (end1 - start));
        System.out.println("时间二:" + (end2  - start));
    }


    @Test
    public void circulationTest() throws InterruptedException, ExecutionException {
        long start = System.currentTimeMillis();
        List<Future<String>> futures = new ArrayList<>();
        List<String> resultList = new ArrayList<>();
        //批量调用异步方法
        for (int i=1;i<=10;i++){
            Future<String> f = asyncTest.circulation(i);
           futures.add(f);
        }
        //全部执行完毕后统一获取
        for (Future<String> future : futures) {
            resultList.add(future.get());
        }
        long end = System.currentTimeMillis();
        System.out.println("结果为:" + resultList);
        System.out.println("耗时:" + (end - start));
    }

}

常见问题:

以下会使@Async注解失效

一、异步方法不能使用static修饰 

二、异步类没有使用@Component注解(或其他注解如@Service)导致spring无法扫描到异步类 

三、@Async标注的异步方法与调用其的方法不能在同一个类中

四、类中需要使用@Autowired或@Resource等注解自动注入,不能自己手动new对象

五、使用SpringBoot框架必须在启动类或配置类中增加@EnableAsync注解

六、在Async 方法上标注@Transactional是没用的。 在Async 方法调用的方法上标注@Transactional 有效

参考文章:

线程池的各种使用场景_星空dream的博客-CSDN博客_线程池的应用场景

@EnableAsync@Async使用总结 - 在贝加尔湖畔 - 博客园 (cnblogs.com)

SpringBoot异步任务, 以及带返回值的异步任务(@Async 不起作用的原因)_晴天小哥哥的博客-CSDN博客_springboot方法异步

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值