纯净、安全、绿色的下载网站

首页|软件分类|下载排行|最新软件|IT学院

当前位置:首页IT学院IT技术

Springboot整合@Async线程池 Springboot 怎样使用@Async整合线程池

赵雷-Nathanial   2021-06-09 我要评论
想了解Springboot 怎样使用@Async整合线程池的相关内容吗赵雷-Nathanial在本文为您仔细讲解Springboot整合@Async线程池的相关知识和一些Code实例欢迎阅读和指正我们先划重点:Springboot线程池,@Async整合线程池下面大家一起来学习吧

Springboot @Async整合线程池

开篇咱们先来聊聊线程池这个概念或者说为什么要使用线程池简言之充分利用cpu资源提高程序执行时间但是相反线程池异常提示、主线程和子线程事务问题也是显而易见的

那么@Async这个注解又是什么做用呢?其实就是标识方法为异步任务的一个注解默认会自己维护一个线程池(存在弊端)利用子线程去执行任务那么如果把这两者结合的话线程池+Async又会有什么效果呢!

循序渐进

提到线程池可以采用Executors提供四种线程池下使用某些特性的场景下还是不错的(简便省事)当然此篇文章就不进行描述我们一块来看看自定义线程池的配置具体参数自行查阅需要注意“核心线程数”、“最大线程数”、“线程队列数值”配置:

自定义线程池

接下来便要将Async和线程池一块使用了使用之前请先到springboot启动类上加上@EnableAsync注解具体使用方式如下:

此时便是开始在主线程里面通过多线程来使用异步任务不过此时也需要分情况考虑这里便提前和大家一块说了

1:异步任务是否和主线程有关联简言之就是两者之间是否相互不影响?

2:如果不影响的话主线程和异步任务的子线程直接使用便可以

3:如果影响的话主线程是要等待子线程执行完的结果的此时便可以考虑加锁、或者使用一些提供好的并发类比如CountDownLatch、CyclicBarrier两者的区别请自行查阅结合项目中的需求我这里使用的前者CountDownLatch 

如果没有其它业务需求的话那么此时到这里便可以正常使用了但是如果碰到子线程中有一些“自定义的提示信息”或者是“自定义的异常信息”如果单纯的考虑在主线程中通过Try…catch或者SpringMVC中全局异常拦截**@RestControllerAdvice**能够进行处理那就大错特错了根本不可能实现的那么此时我们便需要这样使用才可以让主线程中捕获信息然后返回给接口调用方-前端

但是你以为这样就结束了么相对于上面的操作我们可以将子线程是否需要返回提示信息分为以下两种情况:

1:需要返回子线程抛异常后通过主线程返回提示信息那么采用上面图片的处理方式

2:不需要返回子线程抛异常后要在某些地方进行日志记录等等此时可以换一种方式定义线程池具体方式如下本人亲自测试过

子线程异常处理

难道这篇博客的介绍就此结束了休想强行灌输一波主线程和多线程中的子线程事务问题具体情况如下:

1:主线程异常、子线程未执行此情况不会有任何影响

2:主线程先执行子线程异常此时会有影响

2.1:主线程中加入@Transactional(rollbackFor = Exception.class)注解子线程不加事务注解此时如果都正常的话那么主线程和子线程在同一事务里面一块提交反之子线程如果不正常的话那么主线程和子线程都不会进行提交

2.2:主线程加入事务注解子线程中也加入事务注解@Transactional(propagation = Propagation.REQUIRES_NEW)如果都正常的话子线程提交后主线程才会提交反之主线程和子线程都不会进行提交

问题思考

其实在上面设计过程中偏于实践操作的部分多一些当然如果会一些理论知识会更好理解的在项目中使用多线程技术后发现多线程其实也并没有那么难理解更多的都是在定义方面这点也很好解决反复理解和思考就行了

个人认为利用一些锁的方式来解决主线程和子线程协同的问题会更帅一些 !

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

最近在做订单模块用户购买服务类产品之后需要进行预约预约成功之后分别给商家和用户发送提醒短信考虑发短信耗时的情况所以我想用异步的方法去执行于是就在网上看见了Spring的@Async了

但是遇到了许多问题使得@Async无效也一直没有找到很好的文章去详细的说明@Async的正确及错误的使用方法及需要注意的地方这里简单整理了一下遇见的问题Sring是以配置文件的形式来开启@Async而SpringBoot则是以注解的方式开启

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

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

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

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

第一步、配置@Async

一、springBoot启动类的配置:

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

@ServletComponentScan
@SpringBootApplication
@EnableAsync
public class ClubApiApplication {
    public static void main(String[] args) {
        SpringApplication.run(ClubApiApplication.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
        @PostConstruct // 加上该注解项目启动时就执行一次该方法
    @Async("taskExecutor")
    public void sendMessage1() throws InterruptedException {
        log.info("发送短信方法---- 1   执行开始");
        Thread.sleep(5000); // 模拟耗时
        log.info("发送短信方法---- 1   执行结束");
    }
 
    // 发送提醒短信 2
        @PostConstruct // 加上该注解项目启动时就执行一次该方法
    @Async("taskExecutor")
    public void sendMessage2() throws InterruptedException {
 
        log.info("发送短信方法---- 2   执行开始");
        Thread.sleep(2000); // 模拟耗时
        log.info("发送短信方法---- 2   执行结束");
    }
}

代码中的 @Async("taskExecutor") 对应我们自定义线程池中的 @Bean("taskExecutor") 表示使用我们自定义的线程池

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

@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注解

以上为个人经验希望能给大家一个参考也希望大家多多支持


相关文章

猜您喜欢

  • JS内存空间 详解JS内存空间

    想了解详解JS内存空间的相关内容吗lsgxeva在本文为您仔细讲解JS内存空间的相关知识和一些Code实例欢迎阅读和指正我们先划重点:JS,内存空间下面大家一起来学习吧..
  • Java访问修饰符 深度理解Java访问修饰符

    想了解深度理解Java访问修饰符的相关内容吗dz小伟在本文为您仔细讲解Java访问修饰符的相关知识和一些Code实例欢迎阅读和指正我们先划重点:Java四种访问修饰符,Java访问修饰符下面大家一起来学习吧..

网友评论

Copyright 2020 www.fresh-weather.com 【世纪下载站】 版权所有 软件发布

声明:所有软件和文章来自软件开发商或者作者 如有异议 请与本站联系 点此查看联系方式