“你這個線程池配置,是在給公司省電費嗎?”
—— 技術總監看著我精心設計的線程池,發出了靈魂拷問
上週團隊Code Review,我自信地展示了一個“高性能”線程池:
@Bean
public ThreadPoolExecutor threadPool() {
return new ThreadPoolExecutor(
100, // 核心線程:越多越快!
200, // 最大線程:留足buffer!
60L, TimeUnit.SECONDS,
new LinkedBlockingQueue<>(1000), // 大隊列:絕不丟任務!
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy()
);
}
總監沉默了三秒,然後問:“你知道這個配置在高併發的時候,會先拖垮資料庫,再拖垮整個系統嗎?”
真相:線程數 = CPU核心數 × (1 + 等待時間/計算時間)
// 錯誤示範:盲目設置大線程池
ThreadPoolExecutor executor = new ThreadPoolExecutor(
100, 200, 60, TimeUnit.SECONDS,
new LinkedBlockingQueue<>(1000)
);
// 正確做法:根據業務類型設置
int corePoolSize = Runtime.getRuntime().availableProcessors();
// CPU密集型:N+1
ThreadPoolExecutor cpuExecutor = new ThreadPoolExecutor(
corePoolSize + 1, corePoolSize + 1, 0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<>()
);
// IO密集型:2N
ThreadPoolExecutor ioExecutor = new ThreadPoolExecutor(
corePoolSize * 2, corePoolSize * 2, 60L, TimeUnit.SECONDS,
new LinkedBlockingQueue<>(100)
);
真相:無界隊列 = 記憶體洩漏的定時炸彈
// 災難配置:無界隊列
new LinkedBlockingQueue<>(); // 預設Integer.MAX_VALUE
// 當任務產生速度 > 處理速度時:
// 1. 隊列不斷堆積
// 2. 記憶體持續增長
// 3. 最終OOM,系統崩潰
// 生產環境配置:
new LinkedBlockingQueue<>(100); // 設置合理的邊界
new ArrayBlockingQueue<>(200); // 固定大小,快速響應
真相:選錯拒絕策略 = 資料丟失或服務雪崩
// 案例:訂單支付系統
// 錯誤選擇:直接丟棄
new ThreadPoolExecutor.DiscardPolicy(); // 訂單靜默丟失,用戶已付款但系統沒記錄
// 錯誤選擇:拋出異常
new ThreadPoolExecutor.AbortPolicy(); // 用戶體驗差,支付失敗
// 正確選擇:讓調用線程執行
new ThreadPoolExecutor.CallerRunsPolicy(); // 降級方案,保證訂單不丟失
還記得我剛學Redis時寫的“高性能”快取嗎:
@Service
public class CacheService {
// “聰明”的快取設計:永遠不過期,性能最佳!
public User getUser(String userId) {
User user = redisTemplate.opsForValue().get("user:" + userId);
if (user == null) {
user = userMapper.selectById(userId);
redisTemplate.opsForValue().set("user:" + userId, user);
}
return user;
}
}
結果:記憶體爆滿,資料髒讀,上線當天就回滾。
血淚教訓後的正確寫法:
@Service
public class CorrectCacheService {
public User getUser(String userId) {
String cacheKey = "user:" + userId;
// 1. 先查快取
User user = redisTemplate.opsForValue().get(cacheKey);
if (user != null) {
return user;
}
// 2. 快取不存在,查資料庫(防快取擊穿)
synchronized (this) {
// 雙重檢查
user = redisTemplate.opsForValue().get(cacheKey);
if (user != null) {
return user;
}
// 3. 查詢資料庫
user = userMapper.selectById(userId);
if (user != null) {
// 4. 寫入快取,設置過期時間
redisTemplate.opsForValue().set(cacheKey, user, 30, TimeUnit.MINUTES);
} else {
// 5. 快取空值防穿透
redisTemplate.opsForValue().set(cacheKey, new User(), 5, TimeUnit.MINUTES);
}
}
return user;
}
}
// 新手:能跑就行
public void processOrder(Order order) {
// 直接處理訂單
}
// 老手:生產思維
public void processOrder(Order order) {
try {
// 1. 參數校驗
validateOrder(order);
// 2. 日誌記錄
log.info("開始處理訂單: {}", order.getId());
// 3. 異常處理
doProcess(order);
// 4. 監控指標
metrics.recordSuccess();
} catch (Exception e) {
// 5. 錯誤處理
handleProcessError(order, e);
metrics.recordError();
}
}
錯誤思維:Redis很快 → 所有資料都放Redis
正確思維:資料分級存儲 → 熱點放Redis,冷資料放MySQL
// 學費配置:越大越好
spring.datasource.hikari.maximum-pool-size=100
// 正確配置:根據資料庫承受能力
spring.datasource.hikari.maximum-pool-size=20
spring.datasource.hikari.minimum-idle=5
// 學費寫法:大事務
@Transactional
public void createOrder(Order order) {
// 1. 校驗參數(非資料庫操作)
validate(order);
// 2. 寫訂單表
orderMapper.insert(order);
// 3. 更新庫存(可能鎖表很久)
updateStock(order);
// 4. 發消息通知
sendMessage(order);
}
// 正確寫法:拆分事務
public void createOrder(Order order) {
validate(order); // 非事務
orderMapper.insert(order); // 小事務
// 異步處理其他操作
asyncUpdateStock(order);
asyncSendMessage(order);
}
經過無數次的踩坑和填坑,現在我review程式碼時重點關注:
互動話題:
你在專案中踩過最大的坑是什麼?怎麼解決的?在評論區分享你的經歷,點讚最高的送技術書籍一本!