回顧線程的基本知識(shí)
我們最常見的創(chuàng)建線程的幾種方法:一是繼承Thread類,二是實(shí)現(xiàn)Runnable的接口,三是實(shí)現(xiàn)Callable接口。單個(gè)線程的創(chuàng)建和銷毀會(huì)消耗機(jī)器資源,如果線程數(shù)量多的話,頻繁的創(chuàng)建和銷毀會(huì)大大的降低程序運(yùn)行的效率,耗費(fèi)大量的內(nèi)存,因?yàn)檎碚f線程執(zhí)行完畢后死亡,會(huì)被當(dāng)作垃圾回收。怎么能讓線程復(fù)用、統(tǒng)一管理命名線程、提高資源的使用率?線程池就能完美的解決這個(gè)問題。
為什么使用線程池
線程池是一種多線程處理形式,處理過程中將任務(wù)添加到隊(duì)列,線程池在工程啟動(dòng)時(shí)并不會(huì)立即創(chuàng)建大量空閑的線程,程序?qū)⒁粋€(gè)任務(wù)傳給線程池,線程池就會(huì)啟動(dòng)一條線程來執(zhí)行這個(gè)任務(wù)。執(zhí)行結(jié)束以后,該線程并不會(huì)死亡,而是再次返回線程池中成為空閑狀態(tài),等待執(zhí)行下一個(gè)任務(wù)。
簡而言之,線程池就是一組線程的集合。

SpringBoot配置線程池
首先在啟動(dòng)類中配置如下Bean
@Bean("defaultThreadPool")
public ThreadPoolExecutor defaultThreadPool() {
log.info("start asyncServiceExecutor");
ThreadFactory threadFactory = new ThreadFactoryBuilder().setNameFormat("HiveMetric-pool-%d").build();
// 核心線程數(shù)
int corePoolSize = 10;
// 配置最大線程數(shù)
int maximumPoolSize = 20;
// 當(dāng)線程數(shù)大于內(nèi)核數(shù)時(shí),這是多余的空閑線程將在終止之前等待新任務(wù)的最長時(shí)間
long keepAliveTime = 2L;
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime,
TimeUnit.SECONDS, new LinkedBlockingQueue<>(1024), threadFactory, new ThreadPoolExecutor.AbortPolicy());
log.info("線程池,{}", threadPoolExecutor);
return threadPoolExecutor;
}
1)首先創(chuàng)建一個(gè)ThreadFactoryBuilder給線程統(tǒng)一命名,方便管理及檢索日志;
2)通過ThreadPoolExecutor來創(chuàng)建一個(gè)線程池,該類來自于JUC包下,下面介紹一下重要的幾個(gè)參數(shù);
corePoolSize:核心線程數(shù)量
maximumPoolSize:線程池中的線程總數(shù)=核心線程數(shù)+非核心線程數(shù)
keepAliveTime:當(dāng)線程數(shù)大于內(nèi)核數(shù)時(shí),多余的空閑線程將在終止之前等待新任務(wù)的最長時(shí)間。
TimeUnit:時(shí)間單位
BlockingQueue<Runnable>:在執(zhí)行任務(wù)之前用于保留任務(wù)的隊(duì)列。
ThreadFactory:線程工廠
RejectedExecutionHandler:因?yàn)檫_(dá)到了線程界限和隊(duì)列容量而在執(zhí)行被阻止時(shí)使用的處理器。線程池中的線程已經(jīng)用完了,無法繼續(xù)為新任務(wù)服務(wù),同時(shí),等待隊(duì)列也已經(jīng)排滿了,再也塞不下新任務(wù)了。這時(shí)候我們就需要拒絕策略機(jī)制合理的處理這個(gè)問題。JDK 內(nèi)置的拒絕策略如下:
- AbortPolicy : 直接拋出異常,阻止系統(tǒng)正常運(yùn)行。
- CallerRunsPolicy : 只要線程池未關(guān)閉,該策略直接在調(diào)用者線程中,運(yùn)行當(dāng)前被丟棄的
任務(wù)。顯然這樣做不會(huì)真的丟棄任務(wù),但是,任務(wù)提交線程的性能極有可能會(huì)急劇下降。 - DiscardOldestPolicy : 丟棄最老的一個(gè)請求,也就是即將被執(zhí)行的一個(gè)任務(wù),并嘗試再
次提交當(dāng)前任務(wù)。 - DiscardPolicy : 該策略默默地丟棄無法處理的任務(wù),不予任何處理。如果允許任務(wù)丟
失,這是最好的一種方案。
以上內(nèi)置拒絕策略均實(shí)現(xiàn)了RejectedExecutionHandler接口,若以上策略仍無法滿足實(shí)際需要,完全可以自己擴(kuò)展RejectedExecutionHandler接口。
3)在service層通過如下方法注入
@Autowired
private ThreadPoolExecutor defaultThreadPool;
ThreadPoolExecutor繼承自AbstractExecutorService,下面看一下submit()。
/**
* @throws RejectedExecutionException {@inheritDoc}
* @throws NullPointerException {@inheritDoc}
*/
public <T> Future<T> submit(Callable<T> task) {
if (task == null) throw new NullPointerException();
RunnableFuture<T> ftask = newTaskFor(task);
execute(ftask);
return ftask;
}
最后通過Future<V>的get()的方法取出線程的執(zhí)行結(jié)果。
for (HostEntity hostEntity : hiveServer2Info) {
String hostName = hostEntity.getHostName();
Future<JSONObject> submit = defaultThreadPool.submit(() -> stringConvertJsonObject(hostName, getJmxFileContent(hostName, metricFileLocation)));
map.put(hostName, submit);
}
for (Map.Entry<String, Future<JSONObject>> next : map.entrySet()) {
String hostName = next.getKey();
try {
result.put(hostName, next.getValue().get());
} catch (Exception e) {
log.error("線程池獲取返回結(jié)果異常!主機(jī)名:{}", hostName, e);
}
}
之前從多臺(tái)服務(wù)器解析文件由串行改為線程池并行的解決方法,初步優(yōu)化之后線上接口的響應(yīng)時(shí)間由10s+降為1s。