1. 問題描述
客戶提了一個新需求,開發完成后發現查詢一小時內的數據耗時要 7 秒,這客戶肯定不滿意,不滿意就要和領導提,領導不開心了我就要被扣工資!所以就想利用線程池優化一下代碼,提高方法的效率。
2. 初始代碼
點擊查看代碼
// 查詢所有站點
QueryWrapper<Station> stationQW = new QueryWrapper<>();
stationQW.lambda().eq(Station::getRegionCode, region);
List<Station> stations = this.stationMapper.selectList(stationQW);
List<StationPO> stationPOList = new ArrayList<>();
long start = System.currentTimeMillis();
for (Station station : stations) {
long methodStart = System.currentTimeMillis();
String stationCode = station.getStationCode();
StationPO stationPO = new StationPO();
BeanUtils.copyProperties(station, stationPO);
// 總雨量
Float rainFall = stationDataMapper.queryRainFallByTime(startTime, endTime, stationCode);
stationPO.setRainFall(rainFall);
// 平均氣溫
Float avgTemp = stationDataMapper.queryAvgTempByTime(startTime, endTime, stationCode);
stationPO.setAvgTemp(avgTemp);
// 最高氣溫
Float maxTemp = stationDataMapper.queryMaxTempByTime(startTime, endTime, stationCode);
stationPO.setMaxTemp(maxTemp);
// 最低氣溫
Float minTemp = stationDataMapper.queryMinTempByTime(startTime, endTime, stationCode);
stationPO.setMinTemp(minTemp);
// 最大風速
Float maxWind = stationDataMapper.queryMaxWindByTime(startTime, endTime, stationCode);
stationPO.setMaxWind(maxWind);
// 極大風速
Float enormousWind = stationDataMapper.queryEnormousWindByTime(startTime, endTime, stationCode);
stationPO.setEnormousWind(enormousWind);
// 平均濕度
Float avgHumidity = stationDataMapper.queryAvgHumidityByTime(startTime, endTime, stationCode);
stationPO.setAvgHumidity(avgHumidity);
long methodEnd = System.currentTimeMillis();
System.out.println("7個查詢耗時:" + new BigDecimal(methodEnd - methodStart).divide(new BigDecimal(1000)).doubleValue());
stationPOList.add(stationPO);
}
long end = System.currentTimeMillis();
System.out.println("方法耗時:" + new BigDecimal(end - start).divide(new BigDecimal(1000)).doubleValue());
我這邊站點數據集合的大小是37,每次循環都有7個SQL語句,每個SQL的執行時間在0.8秒左右,時間都浪費在循環上了,所以設想循環都創建一個線程去執行任務,這樣的話總耗時也就是一次循環的時間。
3. 用到的技術
- ThreadPoolExecutor線程池
- CountDownLatch鎖
這里簡單說一下CountDownLatch鎖,作用就是一個線程會等待其他線程都執行完畢后再繼續執行,具體是通過一個計數器來實現的,計數器的初始值是線程的數量。每當一個線程執行完畢后,計數器的值就-1,當計數器的值為0時,表示所有線程都執行完畢,然后在閉鎖上等待的線程就可以恢復工作了。
4. 整體思路
首先創建一個線程池,然后創建鎖,這里我直接把線程池的大小以及鎖的count都設置成list的大小,也就是循環次數,開始循環,for循環開啟線程,執行一個站點的查詢數據SQL,查詢完成后關閉一個鎖(countDown方法)。循環外面等待所有線程結束后(await方法),關閉線程池(shutdown方法),結束。
5. 優化后代碼
點擊查看代碼
// 查詢所有站點
QueryWrapper<Station> stationQW = new QueryWrapper<>();
stationQW.lambda().eq(Station::getRegionCode, region);
List<Station> stations = this.stationMapper.selectList(stationQW);
List<StationPO> stationPOList = new ArrayList<>();
ThreadPoolExecutor poolExecutor = ExecutorBuilder.create()
.setCorePoolSize(stations.size()) // 初始線程
.setMaxPoolSize(stations.size()) // 最大線程
.setWorkQueue(new LinkedBlockingQueue<>(100)) // 線程池策略
.build();
CountDownLatch cdl = new CountDownLatch(stations.size());
long start = System.currentTimeMillis();
for (Station station : stations) {
poolExecutor.execute(
() -> {
long methodStart = System.currentTimeMillis();
String stationCode = station.getStationCode();
StationPO stationPO = new StationPO();
BeanUtils.copyProperties(station, stationPO);
// 總雨量
Float rainFall = stationDataMapper.queryRainFallByTime(startTime, endTime, stationCode);
stationPO.setRainFall(rainFall);
// 平均氣溫
Float avgTemp = stationDataMapper.queryAvgTempByTime(startTime, endTime, stationCode);
stationPO.setAvgTemp(avgTemp);
// 最高氣溫
Float maxTemp = stationDataMapper.queryMaxTempByTime(startTime, endTime, stationCode);
stationPO.setMaxTemp(maxTemp);
// 最低氣溫
Float minTemp = stationDataMapper.queryMinTempByTime(startTime, endTime, stationCode);
stationPO.setMinTemp(minTemp);
// 最大風速
Float maxWind = stationDataMapper.queryMaxWindByTime(startTime, endTime, stationCode);
stationPO.setMaxWind(maxWind);
// 極大風速
Float enormousWind = stationDataMapper.queryEnormousWindByTime(startTime, endTime, stationCode);
stationPO.setEnormousWind(enormousWind);
// 平均濕度
Float avgHumidity = stationDataMapper.queryAvgHumidityByTime(startTime, endTime, stationCode);
stationPO.setAvgHumidity(avgHumidity);
long methodEnd = System.currentTimeMillis();
System.out.println("7個查詢耗時:" + new BigDecimal(methodEnd - methodStart).divide(new BigDecimal(1000)).doubleValue());
stationPOList.add(stationPO);
// 閉鎖-1
cdl.countDown();
}
);
}
try {
// 等待所有線程結束
cdl.await();
} catch (InterruptedException e) {
StaticLog.error("線程錯誤:{}",e.getMessage());
}
poolExecutor.shutdown();
long end = System.currentTimeMillis();
System.out.println("方法耗時:" + new BigDecimal(end - start).divide(new BigDecimal(1000)).doubleValue());
6. 自我總結
由於是第一次使用多線程,遇到了很多問題,雖然最后查詢時間縮短到 0.7 秒左右,但是不知道這樣使用多線程合不合理,后續還要繼續學習優化。通過這次開發也發現了自己知識儲備量的不足,果然啊,學Java就得從入門到入土。最終要的就是處理問題的時候態度以及思路,不要急躁,那樣反而會更亂,慢慢來總會解決的,加油!