微信公眾號:[Java技術(shù)干貨]
關(guān)注Java技術(shù)、關(guān)注前端后端全棧技術(shù)。問題或建議,請公眾號留言。
最近在弄一個高并發(fā)項目,經(jīng)過接口壓測后,各項指標(biāo)不禁人意,也一直在搞程序調(diào)優(yōu)(Nginx、Redis、數(shù)據(jù)庫)。
真的是被虐得是體無完膚,發(fā)絲也日漸脆弱。
哎.............還在錘煉中.........
在調(diào)優(yōu)的過程中,我把在程序中遇到多線程優(yōu)化的幾個場景案例記錄分享一下,以供學(xué)習(xí)和交流。
場景一:數(shù)據(jù)拆分多個subList, 分批多線程導(dǎo)入
// map拆分成多個subList
List<Map<String, List<UserParam>>> userParams= MapUtil.mapChunk(userParam, BATCH_COUNT);
AtomicReference<CompletableFuture<Void>> all = new AtomicReference<>();
userParams.stream().forEach(userListMap -> {
// 每一個subList 創(chuàng)建一個線程處理,以下是無參返回
CompletableFuture<Void> cf = CompletableFuture.runAsync(() -> {
try {
// 此處要注意父級線程往子線程的參數(shù)傳遞,不然在子線程中會存在取不到值的情況
List<UserParam> threadTemp = new ArrayList<>();
userListMap.entrySet().stream().forEach(s -> {
threadTemp.addAll(s.getValue());
});
// 保存業(yè)務(wù)數(shù)據(jù)
saveData(threadTemp);
} finally {
}
});
all.set(CompletableFuture.allOf(cf));
});
all.get().join();
場景二:數(shù)據(jù)列表查詢(一個方法多線程處理業(yè)務(wù)) 拆分多線程處理
List<CompletableFuture> comList = new ArrayList<>();
// 該計數(shù)器,是為了等待所有線程都執(zhí)行完了,在往后執(zhí)行
CountDownLatch countDownLatch = new CountDownLatch(1); // 1 代表會初始化1個計數(shù),這個是跟隨創(chuàng)建線程數(shù)量保持一致
CompletableFuture<Void> cf = CompletableFuture.runAsync(() -> {
// 具體寫業(yè)務(wù)的地方
......
}, asyncExecutor); // 采用異步線程交給線程池,避免無限創(chuàng)建線程
// 添加一個異步等待線程去監(jiān)聽cf 是否完成執(zhí)行
CompletableFuture cf2 = cf.whenCompleteAsync((result, error) -> {
// 如果完成,則把計數(shù)器減1
countDownLatch.countDown();
});
comlist.add(cf2);
// 最后固定寫法
CompletableFuture<Void> all = CompletableFuture.allOf(comList.toArray(new CompletableFuture[comList.size()]));
all.join();
try {
countDownLatch.await();
} catch (InterruptedException e) {
log.error("線程中斷異常:{}", e);
}
場景三:多線程帶返回值處理
CountDownLatch countDownLatch = new CountDownLatch(count);
List<CompletableFuture> comlist = new ArrayList<>();
List<PdfContent> resultList = new ArrayList<>();
CompletableFuture<PdfContent> futureResult = CompletableFuture.supplyAsync(()->{
PdfContent result = new PdfContent;
// 業(yè)務(wù)處理 返回result結(jié)果
return result;
}, asyncExecutor);
CompletableFuture futureCompleteResult = futureResult.whenCompleteAsync((result, exception) -> {
resultList.add(result);
countDownLatch.countDown();
});
comlist.add(futureCompleteResult);
// 最后都一樣的處理
CompletableFuture<Void> all = CompletableFuture.allOf(comlist.toArray(new CompletableFuture[count]));
all.join();
try {
countDownLatch.await();
// 對resultList結(jié)果進(jìn)行排序,多線程處理返回結(jié)果是無序的,需要進(jìn)行排序
Collections.sort(resultList, new Comparator<PdfContent>() {
@Override
public int compare(PdfContent o1, PdfContent o2) {
return o1.getIndex() - o2.getIndex();
}
});
} catch (InterruptedException e) {
e.printStackTrace();
}
上面的是我在項目采用多線程優(yōu)化過程中,一些常用的多線程場景寫法,趕快收藏起來。
注意點(diǎn)
在寫多線程的過程中,需要注意線程間的共享變量傳遞,比如Request對象,SecurityContext上下
文。因?yàn)檫@些對象信息是用ThreadLocal 存儲的,如果里面存在多線程,變量值是不會傳遞,需要
在子線程顯式的賦值對象和移除對象。
總結(jié)
經(jīng)過該項目的歷練,也讓我學(xué)習(xí)到整個項目調(diào)優(yōu)全貌過程。
從壓測工具使用和壓測腳本編寫
再到程序調(diào)優(yōu),中間件(Nginx/Redis/Mysql)調(diào)優(yōu)
監(jiān)控程序進(jìn)行性能分析,找到造成QPS不高的問題點(diǎn)
也算是有個比較全面的項目調(diào)優(yōu)經(jīng)驗(yàn)。