在Springboot中憾筏,針對多線程以及線程池,提供了@EnableAsync以及@Async兩個(gè)注解花鹅,只需要簡單配置即可完成多線程氧腰、線程池的開發(fā)。
1 線程池配置
Springboot提供了多線程的配置接口刨肃,只要實(shí)現(xiàn)該接口古拴,并使用@Configuration和@EnableAsync進(jìn)行注解,即可定義一個(gè)線程池之景。
@Configuration
@ComponentScan("com")
@EnableAsync
public class CustomMultiThreadingConfig implements AsyncConfigurer {
@Override
public Executor getAsyncExecutor() {
ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
//如果池中的實(shí)際線程數(shù)小于corePoolSize,無論是否其中有空閑的線程斤富,都會給新的任務(wù)產(chǎn)生新的線程
taskExecutor.setCorePoolSize(5);
//queueCapacity 線程池所使用的緩沖隊(duì)列
taskExecutor.setQueueCapacity(256);
//連接池中保留的最大連接數(shù)。
taskExecutor.setMaxPoolSize(1024);
//線程名稱前綴
taskExecutor.setThreadNamePrefix("Application Thread-");
taskExecutor.initialize();
return taskExecutor;
}
@Override
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
return AsyncConfigurer.super.getAsyncUncaughtExceptionHandler();
}
}
ThreadPoolTaskExecutor的參數(shù):
- int corePoolSize:線程池維護(hù)線程的最小數(shù)量.
- int maximumPoolSize:線程池維護(hù)線程的最大數(shù)量.
- long keepAliveTime:空閑線程的存活時(shí)間.
- TimeUnit unit: 時(shí)間單位,現(xiàn)有納秒,微秒,毫秒,秒枚舉值.
- BlockingQueue<Runnable> workQueue:持有等待執(zhí)行的任務(wù)隊(duì)列.
- RejectedExecutionHandler handler: 用來拒絕一個(gè)任務(wù)的執(zhí)行锻狗,有兩種情況會發(fā)生這種情況满力。
- 一是在execute方法中若addIfUnderMaximumPoolSize(command)為false焕参,即線程池已經(jīng)飽和;
- 二是在execute方法中, 發(fā)現(xiàn)runState!=RUNNING || poolSize == 0,即已經(jīng)shutdown,就調(diào)用ensureQueuedTaskHandled(Runnable command)油额,在該方法中有可能調(diào)用reject叠纷。
2 定義異步任務(wù)
使用的方式非常簡單,一個(gè)標(biāo)注即可解決所有的問題:
@Async
public void demo() {
...
}
也可以將注解加到帶參數(shù)的方法上:
@Async
public void demo(int i) {
...
}
3 在線程中注入Bean的方法
在Springboot中定義多線程并不復(fù)雜潦嘶,網(wǎng)上能夠檢索的內(nèi)容很多涩嚣,基本誤導(dǎo)的東西很少。但實(shí)際開發(fā)中會遇到一些不易發(fā)現(xiàn)的問題掂僵。
個(gè)人使用場景時(shí)這樣的:
- 自己實(shí)現(xiàn)了一個(gè)時(shí)序數(shù)據(jù)流計(jì)算的小框架
- 不同的算法對應(yīng)的不同的計(jì)算任務(wù)航厚,也就是需要單獨(dú)啟動一個(gè)線程
- 算法名稱和類名稱對應(yīng),通過反射機(jī)制啟動
但因?yàn)槊總€(gè)計(jì)算都是單獨(dú)啟動的線程锰蓬,由于業(yè)務(wù)邏輯較為復(fù)雜幔睬,在線程中需要@Autowired注入Bean,但在方法調(diào)用的時(shí)候芹扭,會拋出ava.lang.NullPointerException異常麻顶。
解決思路
針對上述問題的解決方法也很簡單——實(shí)現(xiàn) org.springframework.context.ApplicationContextAware接口
網(wǎng)上找到個(gè)鏈接,里面解釋的很好
https://www.cnblogs.com/codecat/p/11149893.html
“ApplicationContextAware的作用是可以方便獲取Spring容器ApplicationContext舱卡,從而可以獲取容器內(nèi)的Bean辅肾。ApplicationContextAware接口只有一個(gè)方法setApplicationContext,如果實(shí)現(xiàn)了這個(gè)方法轮锥,那么Spring創(chuàng)建這個(gè)實(shí)現(xiàn)類的時(shí)候就會自動執(zhí)行這個(gè)方法矫钓,把ApplicationContext注入到這個(gè)類中,也就是說舍杜,spring 在啟動的時(shí)候就需要實(shí)例化這個(gè) class(如果是懶加載就是你需要用到的時(shí)候?qū)嵗┓莺梗趯?shí)例化這個(gè) class 的時(shí)候,發(fā)現(xiàn)它包含這個(gè) ApplicationContextAware 接口的話蝴簇,sping 就會調(diào)用這個(gè)對象的 setApplicationContext 方法杯活,把 applicationContext Set 進(jìn)去了“敬剩”
代碼如下:
@Component
public class ApplicationContextProvider implements ApplicationContextAware {
/**
* 上下文對象
*/
private static ApplicationContext applicationContext;
/**
* 設(shè)置上下文對象
* @param applicationContext 應(yīng)用上下文
* @throws BeansException Bean異常
*/
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
ApplicationContextProvider.applicationContext = applicationContext;
}
/**
* 獲取上下文對象
*
* @return application context
*/
private static ApplicationContext getApplicationContext() {
return applicationContext;
}
/**
* 通過名稱獲取Bean.
*
* @param name Bean名稱
* @return Object對象
*/
public static Object getBean(String name) {
return getApplicationContext().getBean(name);
}
/**
* 通過類名獲取Bean.
*
* @param clazz 類名
* @param <T> T
* @return T
*/
public static <T> T getBean(Class<T> clazz) {
return getApplicationContext().getBean(clazz);
}
/**
* 通過名稱以及Clazz返回指定的Bean
*
* @param name name
* @param clazz class
* @param <T> class
* @return class
*/
public static <T> T getBean(String name, Class<T> clazz) {
return getApplicationContext().getBean(name, clazz);
}
}
具體使用時(shí):
private DemoClass demoClass = ApplicationContextProvider.getBean(DemoClass.class);
這樣即完成了Bean的注入旁钧。