Springboot 线程池 – @Async 与 使用尚未补充带有返回值的异步接口

本文详细介绍了如何在Springboot应用中整合多线程,通过@Async注解实现异步执行,提高并发处理能力。讲解了线程池配置,包括核心线程数、最大线程数、存活时间、队列大小和拒绝策略,并展示了通过CallerRunsPolicy策略避免任务丢失。最后,通过实例展示了两个请求如何并行执行,验证了多线程的效率提升。
摘要由CSDN通过智能技术生成

传统的请求,只有一个线程去真实执行。高并发的时候就会容易造成堵塞!

@Service
public class AsyncServiceImpl implements AsyncService {

    private static final Logger logger = LoggerFactory.getLogger(AsyncServiceImpl.class);

    @Override
    public void executeAsync() {
        logger.info("Start executeAsync");
        try {
            Thread.sleep(10_000);
            logger.info("当前线程名:"+Thread.currentThread().getName());
        } catch (Exception e) {
            logger.error("executeAsync exception");
        }
        logger.info("end executeAsync");
    }
}

同时发送2个请求,结果如下

由上图得知,两个请求同时进来,线程exec-3正在执行期间,线程exec-9还没有真正拿到业务!当线程3执行完毕后,线程9才拿到业务开始执行!可想而知,这种串行机制,会非常耽误请求响应的时间!所以我们得让我们任务采用并行机制:使用多线程来实现!

Springboot整合 多线程 正文

看一下@Async注解的内容

将方法标记为异步执行候选的注解。也可以在类型级别使用,在这种情况下,所有类型的方法都被认为是异步的。但是请注意, @Async不支持在@Configuration类中声明的方法。
在目标方法签名方面,支持任何参数类型。但是,返回类型被限制为void或java.util.concurrent.Future 。在后一种情况下,您可以声明更具体的org.springframework.util.concurrent.ListenableFuture
或java.util.concurrent.CompletableFuture类型,它们允许与异步任务进行更丰富的交互并通过进一步的处理步骤进行即时组合。
从代理返回的Future句柄将是一个实际的异步Future ,可用于跟踪异步方法执行的结果。但是,由于目标方法需要实现相同的签名,因此它必须返回一个临时的Future句柄,该句柄只是传递一个值:
例如 Spring 的AsyncResult 、EJB 3.1 的javax.ejb.AsyncResult或java.util.concurrent.CompletableFuture.completedFuture(Object) 。

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Async {


    指定异步操作的限定符值。
    可用于确定执行异步操作时要使用的目标执行器,匹配特定Executor或TaskExecutor bean     定义的限定符值(或 bean 名称)。
    当在类级别的@Async注释上指定时,表示给定的执行器应该用于类中的所有方法。 Async#value的方法级别使用始终覆盖在类级别设置的任何值
	String value() default "";

}

下面代码包含了线程池的4种 拒绝策略 RejectedExecutor 下面代码注释有说明

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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;
import java.util.concurrent.ThreadPoolExecutor;

/**
 * @author : zanglikun
 * @date : 2021/12/30 9:39
 * @Version: 1.0
 * @Desc : 线程池的配置
 */
@Configuration
@EnableAsync
public class ExecutorConfig {

    private static final Logger logger = LoggerFactory.getLogger(ExecutorConfig.class);


    // 定义一个线程池 方法名很重要!其他方法使用线程池必须指定此方法!
    @Bean
    public Executor asyncServiceExecutor(){
        logger.info("Start AsyncServiceExecutor");
        ThreadPoolTaskExecutor threadPoolExecutor = new ThreadPoolTaskExecutor();
        // 配置设定核心线程数: 最小存在的线程数,如果当前线程小于队列数,依旧会创建线程!使得总线程数 >= 核心线程数
        threadPoolExecutor.setCorePoolSize(5);
        // 配置最大线程数
        threadPoolExecutor.setMaxPoolSize(5);
        // 配置非核心线程空闲线程存活时间 单位秒 默认是60秒
        threadPoolExecutor.setKeepAliveSeconds(60);
        // 允许运行线程数量为 0   这个一般不设置
        //threadPoolExecutor.setAllowCoreThreadTimeOut(true);
        // 配置待执行任务队列大小 默认是(Interger.MaxValue)
        threadPoolExecutor.setQueueCapacity(10);
        // 配置线程池中线程的前缀名
        threadPoolExecutor.setThreadNamePrefix("Async-service-");

        // 配置阻塞队列满后执行拒绝策略。
        /*
         AbortPolicy 这是默认策略。队列满了,是直接抛出 RejectedExecutionException 异常。
         CallerRunsPolicy 是阻塞新任务进入队列。如果当线程池的线程有空余,才回去读取队列任务执行,才会将阻塞任务加入队列。
         DiscardOldestPolicy 是丢弃队列中靠最前的任务。 如果队列有A、B、C 的3个阻塞,此时没有新的线程空闲,突然来了D线程,A线程就会被D代替。此时队列的排序是 B、C、D
         DiscardPolicy 是队列满了,直接丢弃新的任务。
         */
        threadPoolExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        threadPoolExecutor.initialize();
        logger.info("End AsyncServiceRxecutor");
        return threadPoolExecutor;
    }
}

接下来 在ServiceImpl 的具体方法加入 注解@Async(”线程池的方法“)

@Override
    @Async("asyncServiceExecutor")
    //@Async("这里填写的是你线程池的方法名,此处如果不写,会使用Spring默认的线程池")
    public void executeAsync() {
        logger.info("Start executeAsync");
        try {
            Thread.sleep(10000);
            logger.info("当前线程名:"+Thread.currentThread().getName());
        } catch (Exception e) {
            logger.error("executeAsync exception");
        }
        logger.info("end executeAsync");
    }

结果是:

2021-12-30 18:47:22.597  INFO 15728 --- [Async-service-1] c.x.z.s.serviceImpl.AsyncServiceImpl     : Start executeAsync
2021-12-30 18:47:22.841  INFO 15728 --- [Async-service-2] c.x.z.s.serviceImpl.AsyncServiceImpl     : Start executeAsync
2021-12-30 18:47:32.598  INFO 15728 --- [Async-service-1] c.x.z.s.serviceImpl.AsyncServiceImpl     : 当前线程名:Async-service-1
2021-12-30 18:47:32.598  INFO 15728 --- [Async-service-1] c.x.z.s.serviceImpl.AsyncServiceImpl     : end executeAsync
2021-12-30 18:47:32.842  INFO 15728 --- [Async-service-2] c.x.z.s.serviceImpl.AsyncServiceImpl     : 当前线程名:Async-service-2
2021-12-30 18:47:32.842  INFO 15728 --- [Async-service-2] c.x.z.s.serviceImpl.AsyncServiceImpl     : end executeAsync

Async-service-1 执行的时候 与 Async-service-2 执行时并行处理,不冲突、不阻塞!这就是线程池的异步执行!完结!

特殊说明: 以上文章,均是我实际操作,写出来的笔记资料,不会盗用别人文章!烦请各位,请勿直接盗用!转载记得标注来源!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值