🔧 阿川の電商水電行
Shopify 顧問、維護與客製化
💡
小任務 / 單次支援方案
單次處理 Shopify 修正/微調
⭐️
維護方案
每月 Shopify 技術支援 + 小修改 + 諮詢
🚀
專案建置
Shopify 功能導入、培訓 + 分階段交付

凌晨2點,我刪光了所有“精通多線程”的程式碼

image

“你這個線程池配置,是在給公司省電費嗎?”
—— 技術總監看著我精心設計的線程池,發出了靈魂拷問

那個讓我無地自容的Code Review

上週團隊Code Review,我自信地展示了一個“高性能”線程池:

@Bean
public ThreadPoolExecutor threadPool() {
    return new ThreadPoolExecutor(
        100,  // 核心線程:越多越快!
        200,  // 最大線程:留足buffer!
        60L, TimeUnit.SECONDS,
        new LinkedBlockingQueue<>(1000), // 大隊列:絕不丟任務!
        Executors.defaultThreadFactory(),
        new ThreadPoolExecutor.AbortPolicy()
    );
}

總監沉默了三秒,然後問:“你知道這個配置在高併發的時候,會先拖垮資料庫,再拖垮整個系統嗎?”

線程池配置的三大幻覺

幻覺1:線程越多性能越好

真相:線程數 = 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)
);

幻覺2:隊列越大越安全

真相:無界隊列 = 記憶體洩漏的定時炸彈

// 災難配置:無界隊列
new LinkedBlockingQueue<>(); // 預設Integer.MAX_VALUE

// 當任務產生速度 > 處理速度時:
// 1. 隊列不斷堆積
// 2. 記憶體持續增長  
// 3. 最終OOM,系統崩潰

// 生產環境配置:
new LinkedBlockingQueue<>(100); // 設置合理的邊界
new ArrayBlockingQueue<>(200);  // 固定大小,快速響應

幻覺3:拒絕策略無所謂

真相:選錯拒絕策略 = 資料丟失或服務雪崩

// 案例:訂單支付系統
// 錯誤選擇:直接丟棄
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;
    }
}

從“會用”到“用好”的思維轉變

轉變1:從“功能實現”到“生產就緒”

// 新手:能跑就行
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();
    }
}

轉變2:從“單個技術”到“整體架構”

錯誤思維:Redis很快 → 所有資料都放Redis
正確思維:資料分級存儲 → 熱點放Redis,冷資料放MySQL

那些年我們交過的“學費”

學費1:資料庫連線池配置

// 學費配置:越大越好
spring.datasource.hikari.maximum-pool-size=100

// 正確配置:根據資料庫承受能力
spring.datasource.hikari.maximum-pool-size=20
spring.datasource.hikari.minimum-idle=5

學費2:事務使用不當

// 學費寫法:大事務
@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程式碼時重點關注:

🔴 紅色警報(立即修改)

  • 線程池使用Executors創建(可能OOM)
  • 事務範圍過大(鎖競爭嚴重)
  • 快取沒有設置過期時間(記憶體洩漏)

🟡 黃色警告(需要優化)

  • 循環內執行資料庫查詢(N+1問題)
  • 沒有異常處理(錯誤吞沒)
  • 魔法數字(可維護性差)

🟢 最佳實踐

  • 合理的超時設置
  • 完善的監控告警
  • 優雅的降級策略

互動話題
你在專案中踩過最大的坑是什麼?怎麼解決的?在評論區分享你的經歷,點讚最高的送技術書籍一本!


原文出處:https://juejin.cn/post/7555516614126944271


精選技術文章翻譯,幫助開發者持續吸收新知。

共有 0 則留言


精選技術文章翻譯,幫助開發者持續吸收新知。
🏆 本月排行榜
🥇
站長阿川
📝17   💬8   ❤️4
680
🥈
我愛JS
📝3   💬11   ❤️7
214
🥉
AppleLily
📝1   💬3   ❤️1
69
#4
御魂
💬1  
3
評分標準:發文×10 + 留言×3 + 獲讚×5 + 點讚×1 + 瀏覽數÷10
本數據每小時更新一次
🔧 阿川の電商水電行
Shopify 顧問、維護與客製化
💡
小任務 / 單次支援方案
單次處理 Shopify 修正/微調
⭐️
維護方案
每月 Shopify 技術支援 + 小修改 + 諮詢
🚀
專案建置
Shopify 功能導入、培訓 + 分階段交付