背景
我們在開發中常常會遇到針對某個服務出現響應時間過長,用戶使用體驗感極差,出現這種問題的原因比較多,我們現在就說一下其中的一種情況,for循環遍歷查詢。
前提
首先先來簡單介紹一個多線程,提到多線程,首先要明白這幾個詞彙的意思,進程,線程,併發,並行,多線程,同步。
進程:簡單來講,就是一個正在進行中的程序,比如請求一次接口。
線程:進程中獨立運行的一個子任務就是一個線程,比如請求一次接口的過程就可以是一個線程,也可以啓動多個線程。
併發:指兩個或多個事件在同一時間段內執行。
並行:值兩個或多個時間在同一時間點執行。
同步:通過人爲的控制使多線程訪問公共資源時保證線程安全,比如在公共方法上加鎖(synchronized),線程安全是優化線程性能的前提。
優化前代碼
JSONArray devices = req.getJSONArray("devices");
if(null!=devices&&!devices.isEmpty()){
for(Object device:devices){
saveTodayAICalculate(device); //循環執行方法
}
}
//方法
public void saveTodayAICalculate(JSONObject device){
//執行方法內容
xxx...
}
優化後代碼
JSONArray devices = req.getJSONArray("devices");
if(null!=devices&&!devices.isEmpty()){
try{
ExecutorService es = Executors.newFixedThreadPool(8); //定義線程池
CountDownLatch cdld = new CountDownLatch(devices.size());
for(Object device:devices){
es.execute(new Runnable(){
@Override
public void run() {
saveTodayAICalculate(device,cdld); //執行線程方法
}
});
}
cdld.await();
}catch(Exception e){
logger.error("execute executorServcice error,reason is {}",e);
}finally{
es.shutdown(); //執行完一定要銷燬線程,不然容易出現內存溢出異常
}
}
//線程執行方法
public void saveTodayAICalculate(JSONObject device,CountDownLatch cdl){
try{
xxx...
}catch(Exception e){
xxx...
}finally{
if(null!=cdl){
cdl.countDown();
}
}
}
說明
1 通過java.util.cocarrent.Executors創建線程池ExecutorService,創建的方式有多種,這裏只使用newFixedThreadPool穿件固定大小線程池。
2 使用countDownLatch控制線程的執行。
3 多線程執行完一定要銷燬線程池,不然會出現內存溢出異常。
總結一下,這種多線程的缺陷一是當併發量多時,容易創建大量的線程池,佔用大量cpu和內存,二是當執行線程需要返回數據時不滿足,這種情況可以通過實現Callable接口,然後通過Future獲取返回值。比如:
ExecutorService es = Executors.newFixedThreadPool(8);
List<Future<String>> tasks = new ArrayList<>();
for(int i=0;i<12;i++){
Future<String> future = es.submit(new MyCallable(i));
tasks.add(future);
}
for(Future<String> future : tasks){
System.out.println(future.get());
}
//線程類
public class MyCallable implements Callable<String> {
private Integer k;
public MyCallable(Integer k){
this.k = k;
}
@Override
public String call() throws Exception {
return this.k +":"+ this.toString();
}
}