執行緒池坑中之王!

👉 這是一個或許對你有用的社群
🐱 一對一交流/面試小冊/簡歷最佳化/求職解惑,歡迎加入芋道快速開發平臺知識星球。下面是星球提供的部分資料:
👉這是一個或許對你有用的開源專案
國產 Star 破 10w+ 的開源專案,前端包括管理後臺 + 微信小程式,後端支援單體和微服務架構。
功能涵蓋 RBAC 許可權、SaaS 多租戶、資料許可權、商城、支付、工作流、大屏報表、微信公眾號、ERPCRMAI 大模型等等功能:
  • Boot 多模組架構:https://gitee.com/zhijiantianya/ruoyi-vue-pro
  • Cloud 微服務架構:https://gitee.com/zhijiantianya/yudao-cloud
  • 影片教程:https://doc.iocoder.cn
【國內首批】支援 JDK 17/21 + SpringBoot 3.3、JDK 8/11 + Spring Boot 2.7 雙版本 

前言

執行緒池是 Java 中處理多執行緒的強大工具,但它不僅僅是“直接用就完事”的工具。
很多小夥伴在用執行緒池時,因為配置不當或忽略細節,踩過許多坑。
今天跟大家一起聊聊執行緒池中容易踩的 10 個坑,以及如何避免這些坑,希望對你會有所幫助。
基於 Spring Boot + MyBatis Plus + Vue & Element 實現的後臺管理系統 + 使用者小程式,支援 RBAC 動態許可權、多租戶、資料許可權、工作流、三方登入、支付、簡訊、商城等功能
  • 專案地址:https://github.com/YunaiV/ruoyi-vue-pro
  • 影片教程:https://doc.iocoder.cn/video/

1. 直接使用 Executors 建立執行緒池

許多初學者在建立執行緒池時,直接使用 Executors 提供的快捷方法:

ExecutorService executor = Executors.newFixedThreadPool(

10

);

問題在哪?

  • 無界佇列newFixedThreadPool 使用的佇列是 LinkedBlockingQueue,它是無界佇列,任務堆積可能會導致記憶體溢位。
  • 執行緒無限增長newCachedThreadPool 會無限建立執行緒,在任務量激增時可能耗盡系統資源。

示例:記憶體溢位的風險

ExecutorService executor = Executors.newFixedThreadPool(

2

);

for

 (

int

 i = 

0

; i < 

1000000

; i++) {

    executor.submit(() -> {

try

 {

            Thread.sleep(

1000

);

        } 

catch

 (InterruptedException e) {

            e.printStackTrace();

        }

    });

}

任務數遠大於執行緒數,導致任務無限堆積在佇列中,最終可能導致 OutOfMemoryError

解決辦法

使用 ThreadPoolExecutor,並明確指定引數:

ThreadPoolExecutor executor = 

new

 ThreadPoolExecutor(

2

,

4

,

60L

,

    TimeUnit.SECONDS,

new

 ArrayBlockingQueue<>(

100

), 

// 有界佇列
new

 ThreadPoolExecutor.AbortPolicy() 

// 拒絕策略

);

基於 Spring Cloud Alibaba + Gateway + Nacos + RocketMQ + Vue & Element 實現的後臺管理系統 + 使用者小程式,支援 RBAC 動態許可權、多租戶、資料許可權、工作流、三方登入、支付、簡訊、商城等功能
  • 專案地址:https://github.com/YunaiV/yudao-cloud
  • 影片教程:https://doc.iocoder.cn/video/

2. 錯誤配置執行緒數

很多人隨意配置執行緒池引數,比如核心執行緒數 10,最大執行緒數 100,看起來沒問題,但這可能導致效能問題或資源浪費。

示例:錯誤配置導致的執行緒過載

ThreadPoolExecutor executor = 

new

 ThreadPoolExecutor(

10

// 核心執行緒數
100

// 最大執行緒數
60L

,

    TimeUnit.SECONDS,

new

 ArrayBlockingQueue<>(

10

)

);

for

 (

int

 i = 

0

; i < 

1000

; i++) {

    executor.submit(() -> {

try

 {

            Thread.sleep(

5000

); 

// 模擬耗時任務

        } 

catch

 (InterruptedException e) {

            e.printStackTrace();

        }

    });

}

這種配置在任務激增時,會建立大量執行緒,系統資源被耗盡。

正確配置方式

根據任務型別選擇合理的執行緒數:
  • CPU 密集型 :執行緒數建議設定為 CPU 核心數 + 1
  • IO 密集型 :執行緒數建議設定為 2 * CPU 核心數
示例:
int

 cpuCores = Runtime.getRuntime().availableProcessors();

ThreadPoolExecutor executor = 

new

 ThreadPoolExecutor(

    cpuCores + 

1

,

    cpuCores + 

1

,

60L

,

    TimeUnit.SECONDS,

new

 ArrayBlockingQueue<>(

50

)

);

3. 忽略任務佇列的選擇

任務佇列直接影響執行緒池的行為。如果選錯佇列型別,會帶來很多隱患。

常見佇列的坑

  • 無界佇列 :任務無限堆積。
  • 有界佇列 :佇列滿了會觸發拒絕策略。
  • 優先順序佇列 :容易導致高優先順序任務頻繁搶佔低優先順序任務。

示例:任務堆積導致問題

ThreadPoolExecutor executor = 

new

 ThreadPoolExecutor(

2

,

4

,

60L

,

    TimeUnit.SECONDS,

new

 LinkedBlockingQueue<>()

);

for

 (

int

 i = 

0

; i < 

100000

; i++) {

    executor.submit(() -> System.out.println(Thread.currentThread().getName()));

}

改進方法 :用有界佇列,避免任務無限堆積。
new

 ArrayBlockingQueue<>(

100

);

4. 忘記關閉執行緒池

有些小夥伴用完執行緒池後,忘記呼叫 shutdown(),導致程式無法正常退出。

示例:執行緒池未關閉

ExecutorService executor = Executors.newFixedThreadPool(

5

);

executor.submit(() -> System.out.println(

"任務執行中..."

));

// 執行緒池未關閉,程式一直執行

正確關閉方式

executor.shutdown();

try

 {

if

 (!executor.awaitTermination(

60

, TimeUnit.SECONDS)) {

        executor.shutdownNow();

    }

catch

 (InterruptedException e) {

    executor.shutdownNow();

}

5. 忽略拒絕策略

當任務佇列滿時,執行緒池會觸發拒絕策略,很多人不知道預設策略(AbortPolicy)會直接拋異常。

示例:任務被拒絕

ThreadPoolExecutor executor = 

new

 ThreadPoolExecutor(

1

,

1

,

60L

,

    TimeUnit.SECONDS,

new

 ArrayBlockingQueue<>(

2

),

new

 ThreadPoolExecutor.AbortPolicy() 

// 預設策略

);

for

 (

int

 i = 

0

; i < 

10

; i++) {

    executor.submit(() -> System.out.println(

"任務"

));

}

執行到第四個任務時會丟擲 RejectedExecutionException

改進:選擇合適的策略

  • CallerRunsPolicy:提交任務的執行緒自己執行。
  • DiscardPolicy:直接丟棄新任務。
  • DiscardOldestPolicy:丟棄最老的任務。

6. 任務中未處理異常

執行緒池中的任務丟擲異常時,執行緒池不會直接丟擲,導致很多問題被忽略。

示例:異常被忽略

executor.submit(() -> {

thrownew

 RuntimeException(

"任務異常"

);

});

解決方法

  1. 捕獲任務內部異常:

executor.submit(() -> {

try

 {

thrownew

 RuntimeException(

"任務異常"

);

    } 

catch

 (Exception e) {

        System.err.println(

"捕獲異常:"

 + e.getMessage());

    }

});

  1. 自定義執行緒工廠:

ThreadFactory factory = r -> {

    Thread t = 

new

 Thread(r);

    t.setUncaughtExceptionHandler((thread, e) -> {

        System.err.println(

"執行緒異常:"

 + e.getMessage());

    });

return

 t;

};

7. 阻塞任務佔用執行緒池

如果執行緒池中的任務是阻塞的(如檔案讀寫、網路請求),核心執行緒會被佔滿,影響效能。

示例:阻塞任務拖垮執行緒池

executor.submit(() -> {

    Thread.sleep(

10000

); 

// 模擬阻塞任務

});

改進方法

  • 減少任務的阻塞時間。
  • 增加核心執行緒數。
  • 使用非同步非阻塞方式(如 NIO)。

8. 濫用執行緒池

執行緒池不是萬能的,某些場景直接使用 new Thread() 更簡單。

示例:過度使用執行緒池

一個簡單的短期任務:

ExecutorService executor = Executors.newSingleThreadExecutor();

executor.submit(() -> System.out.println(

"執行任務"

));

executor.shutdown();

這種情況下,用執行緒池反而複雜。

改進方式

new

 Thread(() -> System.out.println(

"執行任務"

)).start();

9. 未監控執行緒池狀態

很多人用執行緒池後,不監控其狀態,導致任務堆積、執行緒耗盡的問題被忽略。

示例:監控執行緒池狀態

System.out.println(

"核心執行緒數:"

 + executor.getCorePoolSize());

System.out.println(

"佇列大小:"

 + executor.getQueue().size());

System.out.println(

"已完成任務數:"

 + executor.getCompletedTaskCount());

結合監控工具(如 JMX、Prometheus),實現即時監控。

10. 動態調整執行緒池引數

有些人線上程池設計時忽略了引數調整的必要性,導致後期效能最佳化困難。

示例:動態調整核心執行緒數

executor.setCorePoolSize(

20

);

executor.setMaximumPoolSize(

50

);

即時調整執行緒池引數,能適應業務的動態變化。

總結

執行緒池是強大的工具,但如果我們日常工作中用得不好也非常容易踩坑。
這篇文章透過實際程式碼示例,我們可以清楚看到執行緒池的問題所在及改進方法。
希望這些內容能幫你避免踩坑,寫出高質量的執行緒池程式碼!
執行緒池用得好,效率槓槓的;用得不好,程式天天崩!

歡迎加入我的知識星球,全面提升技術能力。
👉 加入方式,長按”或“掃描”下方二維碼噢
星球的內容包括:專案實戰、面試招聘、原始碼解析、學習路線。
文章有幫助的話,在看,轉發吧。
謝謝支援喲 (*^__^*)

相關文章