這是java高並發系列第27篇文章。
開發環境:jdk1.8。
案例講解
電商app都有用過吧,商品詳情頁,需要給他們提供一個接口獲取商品相關信息:
- 商品基本信息(名稱、價格、庫存、會員價格等)
- 商品圖片列表
- 商品描述信息(描述信息一般是由富文本編輯的大文本信息)
數據庫中我們用了3張表存儲上面的信息:
- 商品基本信息表:t_goods(字段:id【商品id】、名稱、價格、庫存、會員價格等)
- 商品圖片信息表:t_goods_imgs(字段:id、goods_id【商品id】、圖片路徑),一個商品會有多張圖片
- 商品描述信息表:t_goods_ext(字段:id,goods_id【商品id】、商品描述信息【大字段】)
這需求對於大家來說很簡單吧,偽代碼如下:
public Map<String,Object> detail(long goodsId){
//創建一個map
//step1:查詢商品基本信息,放入map
map.put("goodsModel",(select * from t_goods where id = #gooldsId#));
//step2:查詢商品圖片列表,返回一個集合放入map
map.put("goodsImgsModelList",(select * from t_goods_imgs where goods_id = #gooldsId#));
//step3:查詢商品描述信息,放入map
map.put("goodsExtModel",(select * from t_goods_ext where goods_id = #gooldsId#));
return map;
}
上面這種寫法應該很常見,代碼很簡單,假設上面每個步驟耗時200ms,此接口總共耗時>=600毫秒,其他還涉及到網絡傳輸耗時,估計總共會在700ms左右,此接口有沒有優化的空間,性能能夠提升多少?我們一起來挑戰一下。
在看一下上面的邏輯,整個過程是按順序執行的,實際上3個查詢之間是沒有任何依賴關系,所以說3個查詢可以同時執行,那我們對這3個步驟采用多線程並行執行,看一下最后什么情況,代碼如下:
package com.itsoku.chat26;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.*;
/**
* 跟着阿里p7學並發,微信公眾號:javacode2018
*/
public class Demo1 {
/**
* 獲取商品基本信息
*
* @param goodsId 商品id
* @return 商品基本信息
* @throws InterruptedException
*/
public String goodsDetailModel(long goodsId) throws InterruptedException {
//模擬耗時,休眠200ms
TimeUnit.MILLISECONDS.sleep(200);
return "商品id:" + goodsId + ",商品基本信息....";
}
/**
* 獲取商品圖片列表
*
* @param goodsId 商品id
* @return 商品圖片列表
* @throws InterruptedException
*/
public List<String> goodsImgsModelList(long goodsId) throws InterruptedException {
//模擬耗時,休眠200ms
TimeUnit.MILLISECONDS.sleep(200);
return Arrays.asList("圖1", "圖2", "圖3");
}
/**
* 獲取商品描述信息
*
* @param goodsId 商品id
* @return 商品描述信息
* @throws InterruptedException
*/
public String goodsExtModel(long goodsId) throws InterruptedException {
//模擬耗時,休眠200ms
TimeUnit.MILLISECONDS.sleep(200);
return "商品id:" + goodsId + ",商品描述信息......";
}
//創建個線程池
ExecutorService executorService = Executors.newFixedThreadPool(10);
/**
* 獲取商品詳情
*
* @param goodsId 商品id
* @return
* @throws ExecutionException
* @throws InterruptedException
*/
public Map<String, Object> goodsDetail(long goodsId) throws ExecutionException, InterruptedException {
Map<String, Object> result = new HashMap<>();
//異步獲取商品基本信息
Future<String> gooldsDetailModelFuture = executorService.submit(() -> goodsDetailModel(goodsId));
//異步獲取商品圖片列表
Future<List<String>> goodsImgsModelListFuture = executorService.submit(() -> goodsImgsModelList(goodsId));
//異步獲取商品描述信息
Future<String> goodsExtModelFuture = executorService.submit(() -> goodsExtModel(goodsId));
result.put("gooldsDetailModel", gooldsDetailModelFuture.get());
result.put("goodsImgsModelList", goodsImgsModelListFuture.get());
result.put("goodsExtModel", goodsExtModelFuture.get());
return result;
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
long starTime = System.currentTimeMillis();
Map<String, Object> map = new Demo1().goodsDetail(1L);
System.out.println(map);
System.out.println("耗時(ms):" + (System.currentTimeMillis() - starTime));
}
}
輸出:
{goodsImgsModelList=[圖1, 圖2, 圖3], gooldsDetailModel=商品id:1,商品基本信息...., goodsExtModel=商品id:1,商品描述信息......}
耗時(ms):208
可以看出耗時200毫秒左右,性能提升了2倍,假如這個接口中還存在其他無依賴的操作,性能提升將更加顯著,上面使用了線程池並行去執行3次查詢的任務,最后通過Future獲取異步執行結果。
整個優化過程:
- 先列出無依賴的一些操作
- 將這些操作改為並行的方式
用到的技術有:
總結
- 對於無依賴的操作盡量采用並行方式去執行,可以很好的提升接口的性能
- 大家可以在你們的系統中試試這種方法,感受一下效果,會讓你感覺很爽
java高並發系列目錄
- 第1天:必須知道的幾個概念
- 第2天:並發級別
- 第3天:有關並行的兩個重要定律
- 第4天:JMM相關的一些概念
- 第5天:深入理解進程和線程
- 第6天:線程的基本操作
- 第7天:volatile與Java內存模型
- 第8天:線程組
- 第9天:用戶線程和守護線程
- 第10天:線程安全和synchronized關鍵字
- 第11天:線程中斷的幾種方式
- 第12天JUC:ReentrantLock重入鎖
- 第13天:JUC中的Condition對象
- 第14天:JUC中的LockSupport工具類,必備技能
- 第15天:JUC中的Semaphore(信號量)
- 第16天:JUC中等待多線程完成的工具類CountDownLatch,必備技能
- 第17天:JUC中的循環柵欄CyclicBarrier的6種使用場景
- 第18天:JAVA線程池,這一篇就夠了
- 第19天:JUC中的Executor框架詳解1
- 第20天:JUC中的Executor框架詳解2
- 第21天:java中的CAS,你需要知道的東西
- 第22天:JUC底層工具類Unsafe,高手必須要了解
- 第23天:JUC中原子類,一篇就夠了
- 第24天:ThreadLocal、InheritableThreadLocal(通俗易懂)
- 第25天:掌握JUC中的阻塞隊列
java高並發系列連載中,總計估計會有四五十篇文章。
阿里p7一起學並發,公眾號:路人甲java,每天獲取最新文章!