雜記之@Asyns異步調用的正確打開姿勢

環境:

spring;spring boot

摘要說明:

項目開發過程中往往會出現需異步調用的情況,以便提高系統的響應速度或者提高部分業務的處理時間;

但異步調用需要根據系統的承受能力做好相關配置,而不是放任隨意使用;

如系統批跑5000條數據做相關業務處理;若循環異步處理,則很可能將數據庫給壓垮;

所以這裏需要控制系統異步線程池的大小及線程池的滿額執行策略;

步驟:

1.@Asyns的使用

@Asyns的使用其實很簡單,在系統已允許異步調用的情況下,在bean管理的類的方法上加上此註解就表示該方法可;


import java.util.Date;

import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;

@Component
public class TestAsyncComponent {

	@Async
	// @Async("taskScheduler")
	public void test() {
		try {
			System.out.println("測試異步!!!" + new Date());
		} catch (Exception e) {
			e.printStackTrace();
		}

	}
}

其中@Async表示使用默認線程池;指定線程池需註明線程池id;

 2.傳統spring項目(如ssm)下配置線程池

傳統的spring項目下配置開啓異步調用只需一個spring.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:context="http://www.springframework.org/schema/context"
	xmlns:task="http://www.springframework.org/schema/task"
	xsi:schemaLocation="
	http://www.springframework.org/schema/beans
	http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
	http://www.springframework.org/schema/context
	http://www.springframework.org/schema/context/spring-context-4.0.xsd
	http://www.springframework.org/schema/task
	http://www.springframework.org/schema/task/spring-task-4.0.xsd
	">
<!-- 開啓異步調用,缺省的executor給@Async使用 -->
<task:annotation-driven />
</beans>

指定自定義線程池如下配置:

<?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:context="http://www.springframework.org/schema/context"
	xmlns:task="http://www.springframework.org/schema/task"
	xsi:schemaLocation="
	http://www.springframework.org/schema/beans
	http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
	http://www.springframework.org/schema/context
	http://www.springframework.org/schema/context/spring-context-4.0.xsd
	http://www.springframework.org/schema/task
	http://www.springframework.org/schema/task/spring-task-4.0.xsd
	">
	<!-- 給@Async指定線程池 -->
	<task:annotation-driven executor="asyncExecutor" />
	<!-- 線程池中最小線程數爲20,最大數爲150, 隊列的capacity數爲10,任務完成後,線程池中保留最小線程數20,超出的在10s內未使用將被結束掉,當使用的線程數超出線程池最大線程數且隊列也已滿時,由調用者所在線程來執行相應任務 -->
	<task:executor id="asyncExecutor" pool-size="20-150"
		queue-capacity="10" keep-alive="10" rejection-policy="CALLER_RUNS" />
	
</beans>

其中task中executor的相關配置如下:

  • id:線程池id,要求唯一,可與@Async搭配使用
  • pool-size:core size(最小線程數,缺省爲1)-max size(最大線程池,缺省爲Integer.MAX_VALUE)
  • queue-capacity:當最小的線程數已經被佔用滿後,新的任務會被放進queue裏面,當這個queue的capacity也被佔滿之後,pool裏面會創建新線程處理這個任務,直到總線程數達到了max size。缺省值爲:Integer.MAX_VALUE
  • keep-alive:當系統創建的線程數多於core size時,若keep-alive(秒)沒用調用則收回多於線程
  • rejection-policy:當pool已經達到max size的時候,如何處理新任務:
ABORT(缺省):拋出TaskRejectedException異常,然後不執行 

DISCARD:不執行,也不拋出異常 

DISCARD_OLDEST:丟棄queue中最舊的那個任務 

CALLER_RUNS:不在新線程中執行任務,而是有調用者所在的線程來執行即同步調用

實際線程池創建和銷燬線程如下 

  • 如果此時線程池中的數量小於corePoolSize,即使線程池中的線程都處於空閒狀態,也要創建新的線程來處理被添加的任務。
  • 如果此時線程池中的數量等於 corePoolSize,但是緩衝隊列 workQueue未滿,那麼任務被放入緩衝隊列。
  • 如果此時線程池中的數量大於corePoolSize,緩衝隊列workQueue滿,並且線程池中的數量小於maxPoolSize,建新的線程來處理被添加的任務。
  • 如果此時線程池中的數量大於corePoolSize,緩衝隊列workQueue滿,並且線程池中的數量等於maxPoolSize,那麼通過handler所指定的策略來處理此任務。也就是:處理任務的優先級爲:核心線程corePoolSize、任務隊列workQueue、最大線程 maximumPoolSize,如果三者都滿了,使用handler處理被拒絕的任務。
  • 當線程池中的線程數量大於corePoolSize時,如果某線程空閒時間超過keepAliveTime,線程將被終止。這樣,線程池可以動態的調整池中的線程數。

:建議使用上述的CALLER_RUNS滿額策略,此種策略就可以很好的解決摘要中的批量循環執行業務;當線程池滿的時候自己線程同步執行,當線程池有空閒立刻又是異步執行

 3.spring boot下配置線程池

spring boot下開啓使用@Async可參考之前寫過的一篇文章:https://blog.csdn.net/u010904188/article/details/82893473

這裏主要說下spring boot下線程池的配置:

// 啓動異步調用
    @EnableAsync
    @Configuration
    class TaskPoolConfig {
        // 核心線程數(setCorePoolSize)10:線程池創建時候初始化的線程數
        // 最大線程數(setMaxPoolSize)20:線程池最大的線程數,只有在緩衝隊列滿了之後纔會申請超過核心線程數的線程
        // 緩衝隊列(setQueueCapacity)200:用來緩衝執行任務的隊列
        // 允許線程的空閒時間(setKeepAliveSeconds)60秒:當超過了核心線程出之外的線程在空閒時間到達之後會被銷燬
        // 線程池名的前綴(setThreadNamePrefix):設置好了之後可以方便我們定位處理任務所在的線程池
        // 線程池對拒絕任務的處理策略(setRejectedExecutionHandler):這裏採用了CallerRunsPolicy策略,當線程池沒有處理能力的時候,該策略會直接在 execute
        // 方法的調用線程中運行被拒絕的任務(setWaitForTasksToCompleteOnShutdown);如果執行程序已關閉,則會丟棄該任務
        // setWaitForTasksToCompleteOnShutdown(true)該方法就是這裏的關鍵,用來設置線程池關閉的時候等待所有任務都完成再繼續銷燬其他的Bean,這樣這些異步任務的銷燬就會先於異步線程池的銷燬。
        // 同時,這裏還設置了setAwaitTerminationSeconds(60),該方法用來設置線程池中任務的等待時間,如果超過這個時候還沒有銷燬就強制銷燬,以確保應用最後能夠被關閉,而不是阻塞住。
        @Bean("taskExecutor")
        public Executor taskExecutor() {
            ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
            executor.setCorePoolSize(10);
            executor.setMaxPoolSize(20);
            executor.setQueueCapacity(200);
            executor.setKeepAliveSeconds(60);
            executor.setThreadNamePrefix("taskExecutor-");
            executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
            executor.setWaitForTasksToCompleteOnShutdown(true);
            executor.setAwaitTerminationSeconds(60);
            return executor;
        }
    }

在這裏我們可以看到基本配置都是類似的;就多了個線程銷燬機制和線程等待時間設置;

官方api地址如下:https://docs.spring.io/spring-framework/docs/5.1.4.RELEASE/javadoc-api/org/springframework/scheduling/annotation/EnableAsync.html

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章