欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Springboot 如何使用@Async整合線程池

 更新時間:2021年06月09日 15:27:11   作者:趙雷-Nathanial  
這篇文章主要介紹了Springboot 使用@Async整合線程池的操作,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教

Springboot @Async整合線程池

開篇咱們先來聊聊線程池這個概念,或者說為什么要使用線程池;簡言之,充分利用cpu資源,提高程序執(zhí)行時間,但是相反,線程池異常提示、主線程和子線程事務問題也是顯而易見的。

那么@Async這個注解又是什么做用呢?其實就是標識方法為異步任務的一個注解,默認會自己維護一個線程池(存在弊端),利用子線程去執(zhí)行任務;那么如果把這兩者結(jié)合的話,線程池+Async又會有什么效果呢!

循序漸進

提到線程池,可以采用Executors提供四種線程池下,使用某些特性的場景下,還是不錯的(簡便省事),當然此篇文章就不進行描述,我們一塊來看看自定義線程池的配置,具體參數(shù)自行查閱,需要注意“核心線程數(shù)”、“最大線程數(shù)”、“線程隊列數(shù)值”配置:

自定義線程池

接下來便要將Async和線程池一塊使用了,使用之前請先到springboot啟動類上加上@EnableAsync注解,具體使用方式如下:

此時便是開始在主線程里面通過多線程來使用異步任務,不過此時也需要分情況考慮,這里便提前和大家一塊說了。

1:異步任務是否和主線程有關聯(lián),簡言之,就是兩者之間是否相互不影響?

2:如果不影響的話,主線程和異步任務的子線程直接使用便可以;

3:如果影響的話,主線程是要等待子線程執(zhí)行完的結(jié)果的,此時便可以考慮加鎖、或者使用一些提供好的并發(fā)類,比如CountDownLatch、CyclicBarrier,兩者的區(qū)別請自行查閱,結(jié)合項目中的需求,我這里使用的前者CountDownLatch 。

如果沒有其它業(yè)務需求的話,那么此時到這里便可以正常使用了,但是如果碰到子線程中有一些“自定義的提示信息”,或者是“自定義的異常信息”,如果單純的考慮在主線程中通過Try…catch,或者SpringMVC中全局異常攔截**@RestControllerAdvice**能夠進行處理,那就大錯特錯了,根本不可能實現(xiàn)的;那么此時我們便需要這樣使用,才可以讓主線程中捕獲信息,然后返回給接口調(diào)用方-前端。

但是你以為這樣就結(jié)束了么,相對于上面的操作,我們可以將子線程是否需要返回提示信息分為以下兩種情況:

1:需要返回,子線程拋異常后,通過主線程返回提示信息,那么采用上面圖片的處理方式

2:不需要返回,子線程拋異常后,要在某些地方進行日志記錄等等,此時可以換一種方式定義線程池,具體方式如下,本人親自測試過。

子線程異常處理

難道這篇博客的介紹就此結(jié)束了,休想,強行灌輸一波,主線程和多線程中的子線程事務問題,具體情況如下:

1:主線程異常、子線程未執(zhí)行,此情況不會有任何影響

2:主線程先執(zhí)行,子線程異常,此時會有影響;

2.1:主線程中加入@Transactional(rollbackFor = Exception.class)注解,子線程不加事務注解,此時如果都正常的話,那么主線程和子線程在同一事務里面,一塊提交;反之子線程如果不正常的話,那么主線程和子線程都不會進行提交;

2.2:主線程加入事務注解,子線程中也加入事務注解@Transactional(propagation = Propagation.REQUIRES_NEW),如果都正常的話,子線程提交后主線程才會提交;反之主線程和子線程都不會進行提交。

問題思考

其實在上面設計過程中,偏于實踐操作的部分多一些,當然如果會一些理論知識會更好理解的;在項目中使用多線程技術后,發(fā)現(xiàn)多線程其實也并沒有那么難理解,更多的都是在定義方面,這點也很好解決,反復理解和思考就行了;

個人認為,利用一些鎖的方式來解決主線程和子線程協(xié)同的問題會更帥一些 !

SpringBoot線程池的創(chuàng)建、@Async配置步驟及注意事項

最近在做訂單模塊,用戶購買服務類產(chǎn)品之后,需要進行預約,預約成功之后分別給商家和用戶發(fā)送提醒短信??紤]發(fā)短信耗時的情況所以我想用異步的方法去執(zhí)行,于是就在網(wǎng)上看見了Spring的@Async了。

但是遇到了許多問題,使得@Async無效,也一直沒有找到很好的文章去詳細的說明@Async的正確及錯誤的使用方法及需要注意的地方,這里簡單整理了一下遇見的問題,Sring是以配置文件的形式來開啟@Async,而SpringBoot則是以注解的方式開啟。

我們可以使用springBoot默認的線程池,不過一般我們會自定義線程池(因為比較靈活),配置方式有:

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

2、使用Java代碼結(jié)合@Configuration進行配置(推薦使用)

下面分別實現(xiàn)兩種配置方式

第一步、配置@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同目錄下創(chuàng)建文件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">
        <!-- 核心線程數(shù),默認為1 -->
        <property name="corePoolSize" value="10" />
 
        <!-- 最大線程數(shù),默認為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:主線程直接執(zhí)行該任務,執(zhí)行完之后嘗試添加下一個任務到線程池中,可以有效降低向線程池內(nèi)添加任務的速度 -->
            <!-- DiscardOldestPolicy:拋棄舊的任務、暫不支持;會導致被丟棄的任務無法再次被執(zhí)行 -->
            <!-- DiscardPolicy:拋棄當前任務、暫不支持;會導致被丟棄的任務無法再次被執(zhí)行 -->
            <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" />

第二步:創(chuàng)建兩個異步方法的類,如下所示

第一個類(這里模擬取消訂單后發(fā)短信,有兩個發(fā)送短信的方法):

@Service
public class TranTest2Service {
    // 發(fā)送提醒短信 1
    @Async
    public void sendMessage1() throws InterruptedException {
        System.out.println("發(fā)送短信方法---- 1   執(zhí)行開始");
        Thread.sleep(5000); // 模擬耗時
        System.out.println("發(fā)送短信方法---- 1   執(zhí)行結(jié)束");
    }
 
    // 發(fā)送提醒短信 2
    @Async
    public void sendMessage2() throws InterruptedException {
        System.out.println("發(fā)送短信方法---- 2   執(zhí)行開始");
        Thread.sleep(2000); // 模擬耗時
        System.out.println("發(fā)送短信方法---- 2   執(zhí)行結(jié)束");
    }
}

第二個類。調(diào)用發(fā)短信的方法 (異步方法不能與被調(diào)用的異步方法在同一個類中,否則無效):

@Service
public class OrderTaskServic {
    @Autowired
    private TranTest2Service tranTest2Service;
 
    // 訂單處理任務
    public void orderTask() throws InterruptedException {
        this.cancelOrder(); // 取消訂單
        tranTest2Service.sendMessage1(); // 發(fā)短信的方法   1
        tranTest2Service.sendMessage2(); // 發(fā)短信的方法  2
    }
 
    // 取消訂單
    public void cancelOrder() throws InterruptedException {
        System.out.println("取消訂單的方法執(zhí)行------開始");
        System.out.println("取消訂單的方法執(zhí)行------結(jié)束 ");
    }
}

經(jīng)過測試得到如下結(jié)果:

1.沒有使用@Async

2.使用了@Async

可以看出,沒有使用@Async方式實現(xiàn)的發(fā)送短信是同步執(zhí)行的,意思就是說第一條發(fā)送之后再發(fā)送第二條,第二條發(fā)送成功之后再給用戶提示,這樣顯然會影響用戶體驗,再看使用了@Async實現(xiàn)的,在執(zhí)行第一個發(fā)送短信方法之后馬上開啟另一個線程執(zhí)行第二個方法,顯然這樣我們的處理速度回快很多。

使用Java代碼結(jié)合@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 {
 
/** 
 *   默認情況下,在創(chuàng)建了線程池后,線程池中的線程數(shù)為0,當有任務來之后,就會創(chuàng)建一個線程去執(zhí)行任務,
 *    當線程池中的線程數(shù)目達到corePoolSize后,就會把到達的任務放到緩存隊列當中;
 *  當隊列滿了,就繼續(xù)創(chuàng)建線程,當線程數(shù)量大于等于maxPoolSize后,開始使用拒絕策略拒絕 
 */
 
    /** 核心線程數(shù)(默認線程數(shù)) */
    private static final int corePoolSize = 20;
    /** 最大線程數(shù) */
    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:由調(diào)用線程(提交任務的線程)處理該任務
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        // 初始化
        executor.initialize();
        return executor;
    }
}

2.創(chuàng)建兩個異步方法的類(和之前的類類似僅僅是方法上注解不一樣),如下所示:

第一個類(這里模擬取消訂單后發(fā)短信,有兩個發(fā)送短信的方法):

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);
 
    // 發(fā)送提醒短信 1
        @PostConstruct // 加上該注解項目啟動時就執(zhí)行一次該方法
    @Async("taskExecutor")
    public void sendMessage1() throws InterruptedException {
        log.info("發(fā)送短信方法---- 1   執(zhí)行開始");
        Thread.sleep(5000); // 模擬耗時
        log.info("發(fā)送短信方法---- 1   執(zhí)行結(jié)束");
    }
 
    // 發(fā)送提醒短信 2
        @PostConstruct // 加上該注解項目啟動時就執(zhí)行一次該方法
    @Async("taskExecutor")
    public void sendMessage2() throws InterruptedException {
 
        log.info("發(fā)送短信方法---- 2   執(zhí)行開始");
        Thread.sleep(2000); // 模擬耗時
        log.info("發(fā)送短信方法---- 2   執(zhí)行結(jié)束");
    }
}

代碼中的 @Async("taskExecutor") 對應我們自定義線程池中的 @Bean("taskExecutor") ,表示使用我們自定義的線程池。

第二個類。調(diào)用發(fā)短信的方法 (異步方法不能與被調(diào)用的異步方法在同一個類中,否則無效):

@Service
public class OrderTaskServic {
    @Autowired
    private TranTest2Service tranTest2Service;
    // 訂單處理任務
    public void orderTask() throws InterruptedException {
 
        this.cancelOrder(); // 取消訂單
        tranTest2Service.sendMessage1(); // 發(fā)短信的方法   1
        tranTest2Service.sendMessage2(); // 發(fā)短信的方法  2
    }
 
    // 取消訂單
    public void cancelOrder() throws InterruptedException {
        System.out.println("取消訂單的方法執(zhí)行------開始");
        System.out.println("取消訂單的方法執(zhí)行------結(jié)束 ");
    }
}

運行截圖:

注意看,截圖中的 [nio-8090-exec-1] 是Tomcat的線程名稱

[Async-Service-1]、[Async-Service-2]表示線程1和線程2 ,是我們自定義的線程池里面的線程名稱,我們在配置類里面定義的線程池前綴:

private static final String threadNamePrefix = "Async-Service-"; // 線程池名前綴,說明我們自定義的線程池被使用了

注意事項

如下方式會使@Async失效

  • 異步方法使用static修飾
  • 異步類沒有使用@Component注解(或其他注解)導致spring無法掃描到異步類
  • 異步方法不能與被調(diào)用的異步方法在同一個類中
  • 類中需要使用@Autowired或@Resource等注解自動注入,不能自己手動new對象
  • 如果使用SpringBoot框架必須在啟動類中增加@EnableAsync注解

以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。

相關文章

  • spring web.xml指定配置文件過程解析

    spring web.xml指定配置文件過程解析

    這篇文章主要介紹了spring web.xml指定配置文件過程解析,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下
    2020-02-02
  • Mybatis中updateBatch實現(xiàn)批量更新

    Mybatis中updateBatch實現(xiàn)批量更新

    本文主要介紹了Mybatis中updateBatch實現(xiàn)批量更新,文中通過示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2022-03-03
  • Java編程中的條件判斷之if語句的用法詳解

    Java編程中的條件判斷之if語句的用法詳解

    這篇文章主要介紹了Java編程中的條件判斷之if語句的用法詳解,是Java入門學習中的基礎知識,需要的朋友可以參考下
    2015-11-11
  • 詳解Spring Data Jpa當屬性為Null也更新的完美解決方案

    詳解Spring Data Jpa當屬性為Null也更新的完美解決方案

    這篇文章主要介紹了詳解Spring Data Jpa當屬性為Null也更新的完美解決方案,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2019-02-02
  • Java中的ReentrantReadWriteLock使用詳解

    Java中的ReentrantReadWriteLock使用詳解

    這篇文章主要介紹了Java中的ReentrantReadWriteLock使用詳解,ReentrantReadWriteLock是Java中的一個鎖實現(xiàn),它提供了讀寫分離的功能,這種讀寫分離的機制可以提高并發(fā)性能,特別適用于讀多寫少的場景,需要的朋友可以參考下
    2023-11-11
  • RocketMQ?broker?消息投遞流程處理PULL_MESSAGE請求解析

    RocketMQ?broker?消息投遞流程處理PULL_MESSAGE請求解析

    這篇文章主要為大家介紹了RocketMQ?broker?消息投遞流程處理PULL_MESSAGE請求源碼解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2023-04-04
  • SpringBoot中RestTemplate的使用詳解

    SpringBoot中RestTemplate的使用詳解

    這篇文章主要介紹了SpringBoot中RestTemplate的使用詳解,RestTemplate是由Spring框架提供的一個可用于應用中調(diào)用rest服務的類它簡化了與http服務的通信方式,統(tǒng)一了RESTFul的標準,封裝了http連接,我們只需要傳入url及其返回值類型即可,需要的朋友可以參考下
    2023-10-10
  • JAVA文件讀寫例題實現(xiàn)過程解析

    JAVA文件讀寫例題實現(xiàn)過程解析

    這篇文章主要介紹了JAVA文件讀寫例題實現(xiàn)過程解析,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下
    2020-06-06
  • 基于StringUtils工具類的常用方法介紹(必看篇)

    基于StringUtils工具類的常用方法介紹(必看篇)

    下面小編就為大家?guī)硪黄赟tringUtils工具類的常用方法介紹(必看篇)。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2017-07-07
  • Spring Boot啟動時調(diào)用自己的非web邏輯

    Spring Boot啟動時調(diào)用自己的非web邏輯

    在spring Boot中,有些代碼是WEB功能,例如API等,但是有些邏輯是非WEB,啟動時就要調(diào)用并持續(xù)運行的,該如何加載自己的非WEB邏輯呢,下面通過實例代碼給大家講解,一起看看吧
    2017-07-07

最新評論