一、使用@Async在SpringBoot項目中實現多線程
1. 多線程Configuration
啓動類:
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.support.SpringBootServletInitializer;
import org.springframework.context.annotation.ComponentScan;
@SpringBootApplication
@EnableAutoConfiguration
@ComponentScan("com.asiainfo.*")
public class Application extends SpringBootServletInitializer {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(Application.class);
}
}
**@EnableAutoConfiguration:**幫助SpringBoot應用將所有符合條件的@Configuration配置都加載到當前SpringBoot創建並使用的IoC容器。
多線程配置類:
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
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.Hashtable;
import java.util.List;
import java.util.concurrent.Executor;
import java.util.concurrent.FutureTask;
@Configuration
@EnableAsync
public class ExecutorConfig {
private static final Logger logger = LoggerFactory.getLogger(ExecutorConfig.class);
private static Hashtable<String, List<FutureTask>> rejectTaskMap;
@Value("${thread.CORE_POOL_SIZE}")
private int corePoolSize;
@Value("${thread.MAX_POOL_SIZE}")
private int maxPoolSize;
@Value("${thread.QUEUE_CAPACITY}")
private int queueCapacity;
@Bean
public Executor asyncServiceExecutor() {
logger.info("start asyncServiceExecutor");
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
//配置核心線程數
executor.setCorePoolSize(corePoolSize);
//配置最大線程數
executor.setMaxPoolSize(maxPoolSize);
//配置隊列最大長度
executor.setQueueCapacity(queueCapacity);
// rejection-policy:當pool已經達到max size的時候,如何處理新任務
// CALLER_RUNS:不在新線程中執行任務,而是有調用者所在的線程來執行
//自定義拒絕策略
executor.setRejectedExecutionHandler(new MyRejectedPolicyHandler());
rejectTaskMap = new Hashtable<>();
//執行初始化
executor.initialize();
return executor;
}
public static Hashtable<String, List<FutureTask>> getRejectTaskMap() {
return rejectTaskMap;
}
}
@Configuration:
@Configuration用於定義配置類,可替換xml配置文件,被註解的類內部包含有一個或多個被@Bean註解的方法,這些方法將會被AnnotationConfigApplicationContext或AnnotationConfigWebApplicationContext類進行掃描,並用於構建bean定義,初始化Spring容器。
注意:@Configuration註解的配置類有如下要求:
- @Configuration不可以是final類型;
- @Configuration不可以是匿名類;
- 嵌套的configuration必須是靜態類;
@EnableAsync:
以異步執行,允許開啓多線程。
executor.setRejectedExecutionHandler(new MyRejectedPolicyHandler());
設置拒絕策略,當任務源源不斷的過來,而我們的系統又處理不過來的時候,我們要採取的策略是拒絕服務。RejectedExecutionHandler接口提供了拒絕任務處理的自定義方法的機會。在ThreadPoolExecutor中已經包含四種處理策略。
- CallerRunsPolicy:線程調用運行該任務的 execute 本身。此策略提供簡單的反饋控制機制,能夠減緩新任務的提交速度。
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) { if
(!e.isShutdown()) { r.run(); }}
這個策略顯然不想放棄執行任務。但是由於池中已經沒有任何資源了,那麼就直接使用調用該execute的線程本身來執行。(開始我總不想丟棄任務的執行,但是對某些應用場景來講,很有可能造成當前線程也被阻塞。如果所有線程都是不能執行的,很可能導致程序沒法繼續跑了。需要視業務情景而定吧。) - AbortPolicy:處理程序遭到拒絕將拋出運行時 RejectedExecutionException public void
rejectedExecution(Runnable r, ThreadPoolExecutor e) {throw new
RejectedExecutionException();}
這種策略直接拋出異常,丟棄任務。(jdk默認策略,隊列滿併線程滿時直接拒絕添加新任務,並拋出異常,所以說有時候放棄也是一種勇氣,爲了保證後續任務的正常進行,丟棄一些也是可以接收的,記得做好記錄) - DiscardPolicy:不能執行的任務將被刪除 public void rejectedExecution(Runnable r,
ThreadPoolExecutor e) {} 這種策略和AbortPolicy幾乎一樣,也是丟棄任務,只不過他不拋出異常。 - DiscardOldestPolicy:如果執行程序尚未關閉,則位於工作隊列頭部的任務將被刪除,然後重試執行程序(如果再次失敗,則重複此過程)
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) { if
(!e.isShutdown()) {e.getQueue().poll();e.execute®; }}
該策略就稍微複雜一些,在pool沒有關閉的前提下首先丟掉緩存在隊列中的最早的任務,然後重新嘗試運行該任務。這個策略需要適當小心。
除上述四種策略外,還可以添加自定義拒絕策略, MyRejectedPolicyHandler就是一個自定義決絕策略,下文中會着重講一下該策略的實現方式。
設置線程池:
#多線程配置
thread:
CORE_POOL_SIZE: 10
MAX_POOL_SIZE: 100
QUEUE_CAPACITY: 1000
@Value("${thread.CORE_POOL_SIZE}")
private int corePoolSize;
設置核心線程數量。
@Value("${thread.MAX_POOL_SIZE}")
private int maxPoolSize;
設置最大線程數量。
@Value("${thread.QUEUE_CAPACITY}")
private int queueCapacity;
設置緩衝隊列大小。
2. 使用Runner啓動項目
SpringBoot給我們提供了兩個接口來幫助我們實現容器啓動完成後立即執行。這兩個接口分別爲CommandLineRunner和ApplicationRunner。
定義一個類SimosApplicationRunner實現ApplicationRunner接口,然後Override這個ApplicationRunner接口的run方法即可。
Runner:
import com.asiainfo.processor_other.config.MyRejectedPolicy;
import com.asiainfo.processor_other.task.TestTask;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
@Component
@Order(1)
public class TestRunner implements ApplicationRunner {
@Autowired
TestRunner testRunner;
@Autowired
TestTask testTask;
@Override
public void run(ApplicationArguments args) throws Exception {
testRunner.test();
}
@MyRejectedPolicy("runTest")
private void test() {
for (int i = 0; i < 100; i++) {
testTask.runTest(i);
}
}
}
Task:
使用 @Async註解,每調用一次TestTask的runTest方法都會開啓一個新的線程;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
@Component
public class TestTask {
@Async("asyncServiceExecutor")
public void runTest(int i) {
System.out.printf("Test:" + i);
}
}
二、自定義拒絕策略
自定義拒絕策略思路:
若線程池配置不合理,或者任務添加的速度大於處理的速度,會執行線程池拒絕策略,四個系統默認的拒絕策略,或者阻塞主進程,或者拋出異常,或者丟棄任務,在某些情況都不適用的情況下需要自定義拒絕策略經行容災。
改造的思路是自定義拒絕策略,將線程池拒絕的任務緩存到內存中,再在合適的時機重新放入線程池中處理,從而達到了線程池防阻塞、容災的目的(方案使用的內存對象爲線程安全對象,效率較低,只可作爲特定情況下的容災機制使用)。
改造點:
- 修改默認緩存隊列大小(默認:2147483647),改爲1000
- 初始化一個全局的,線程安全的MAP<String,List>,用來代替原有的線程池緩存隊列。其中,String 爲方法名稱, List 爲task(FutureTask)線程隊列。
- 自定義拒絕策略。 當任務拒絕時,將拒絕的任務添加到Map中。
- 自定義註解類,註解到添加任務到線程池的方法上。原有的方法需要重新抽取。
- 通過AOP,Around方式截取註解的方法。判斷全局Map 中是否有對應此方法的線程隊列,如果有,則先執行在MAP緩存隊列中的task,返回null,不再往線程池中添加新的task,如果MAP緩存隊列中沒有此方法對應的task,則正常添加task到線程池中。
代碼實現:
1、ExecutorConfig
參考上文代碼,
2、MyRejectedPolicyHandler
自定義拒絕策略類
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Hashtable;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.FutureTask;
import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.ThreadPoolExecutor;
/**
* @Description: 線程池自定義拒絕策略
* @Date: 2018/12/26 11:25
**/
public class MyRejectedPolicyHandler implements RejectedExecutionHandler {
private static Logger logger = LoggerFactory.getLogger(MyRejectedPolicyHandler.class);
/**
* @Description: 將拒絕的線程放到全局變量中
* @Date: 2018/12/26 11:25
* @Param: [r, executor]
* @Return: void
**/
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
FutureTask task = (FutureTask)r;
// 獲取調用的方法名稱(反射獲取私有屬性)
try {
Object callable = getFiled(task,"callable");
Method userDeclaredMethod = (Method) getFiled(callable,"val$userDeclaredMethod");
String methodName = userDeclaredMethod.getName();
logger.info("Add task to Map, methodName: [ " + methodName + " ]");
//在內存中維護一個全局Map, 將策略拒絕的task放置到map中
Hashtable<String, List<FutureTask>> rejectTaskMap = ExecutorConfig.getRejectTaskMap();
if(!rejectTaskMap.containsKey(methodName)){
List<FutureTask> taskList = new CopyOnWriteArrayList<>();
rejectTaskMap.put(methodName, taskList);
}
List<FutureTask> taskList = rejectTaskMap.get(methodName);
taskList.add(task);
logger.info("MethodName : [ " + methodName + " ] taskList size : [" + taskList.size() + " ]" );
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
/**
* @Description: 反射,通過循環父類獲取field值(含private)
* @Date: 2018/12/26 15:28
* @Param: [c, name]
* @Return: java.lang.Object
**/
private static Object getFiled(Object c, String name) throws IllegalAccessException {
while (c != null && !c.getClass().getName().toLowerCase().equals("java.lang.object")) {
try {
Field field = c.getClass().getDeclaredField(name);
field.setAccessible(true);
return field.get(c);
} catch (NoSuchFieldException e) {
c = c.getClass().getSuperclass();
}
}
return null;
}
}
3、MyRejectedPolicy
自定義拒絕策略註解類
import java.lang.annotation.*;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyRejectedPolicy {
String value() default "";
}
4、MyRejectedPolicyAspect
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.*;
import java.util.concurrent.Executor;
import java.util.concurrent.FutureTask;
/**
* @Description: 判斷線程類任務是否有積留,處理
* @Date: 2018/12/26 16:41
**/
@Aspect
@Component
public class MyRejectedPolicyAspect {
private static Logger logger = LoggerFactory.getLogger(MyRejectedPolicyAspect.class);
@Resource
private Executor asyncServiceExecutor;
@Around("@annotation(MyRejectedPolicy)")
public Object doAroundMethod(ProceedingJoinPoint pjd) throws Throwable {
//取得 PermissionContext 註解屬性(值)信息
MethodSignature methodSignature = (MethodSignature)pjd.getSignature();
MyRejectedPolicy myRejectPolicy = methodSignature.getMethod().getAnnotation(MyRejectedPolicy.class);
String methodName = myRejectPolicy.value();
// 判斷內存維護的列表中是否有此方法產生的task
Hashtable<String, List<FutureTask>> rejectTaskMap = ExecutorConfig.getRejectTaskMap();
if(rejectTaskMap.containsKey(methodName)){
List<FutureTask> taskList = rejectTaskMap.get(methodName);
// 如果有此方法對應的緩存task,不再往線程池中添加新的task,執行緩存中未執行的task,
int taskListSize = taskList.size();
if(taskListSize > 0){
logger.info("[ " + methodName + " ] method blocked, list size: [" + taskListSize + "]");
Iterator<FutureTask> it = taskList.iterator();
while (it.hasNext()){
asyncServiceExecutor.execute(it.next());
it.remove();
}
// return 是爲了打斷後續執行, 不再往線程池中添加新的task
return null;
}
}
return pjd.proceed();
}
}
5、使用方式
參考Runner啓動部分代碼。
在分發線程的部分增加註解,註解值爲Map中的key。
run方法中調用註解方法需要注入自身對象,否則切面無法正常捕獲。
Task代碼中的異步方法需要增加@Async註解,表明爲一個異步方法。