【搞定面試官】你還在用Executors來創建線程池?會有什麼問題呢?

来源:https://www.cnblogs.com/LoveBell/archive/2019/12/03/11979958.html
-Advertisement-
Play Games

前言 "上文" 我們介紹了JDK中的線程池框架 。我們知道,只要需要創建線程的情況下,即使是在單線程模式下,我們也要儘量使用 。即: 但是,在 "《阿裡巴巴Java開發手冊》" 中有一條 【強制】線程池不允許使用 Executors 去創建,而是通過 ThreadPoolExecutor 的方式,這 ...


前言

上文我們介紹了JDK中的線程池框架Executor。我們知道,只要需要創建線程的情況下,即使是在單線程模式下,我們也要儘量使用Executor。即:

ExecutorService fixedThreadPool = Executors.newFixedThreadPool(1); 
//此處不該利用Executors工具類來初始化線程池

但是,在《阿裡巴巴Java開發手冊》中有一條

【強制】線程池不允許使用 Executors 去創建,而是通過 ThreadPoolExecutor 的方式,這樣的處理方式讓寫的同學更加明確線程池的運行規則,規避資源耗盡的風險。

Executors 返回的線程池對象的弊端如下:
FixedThreadPool 和 SingleThreadPool : 允許的請求隊列長度為 Integer.MAX_VALUE,可能會堆積大量的請求,從而導致 OOM。
CachedThreadPool 和 ScheduledThreadPool : 允許的創建線程數量為 Integer.MAX_VALUE,可能會創建大量的線程,從而導致 OOM。

可以看到,這是一個強制性的規則,並且是不允許使用Executors來創建,建議使用ThreadPoolExecutor來創建線程池,那我們先來回顧一下ExecutorsThreadPoolExecutor

我們可以看到ThreadPoolExecutor已經是Executor的具體實現了,而且具有較多可配參數(可配參數見下方,可僅瞭解,用到時再進行詳細查詢)。Executors是一個創建線程池的工具類,查看其源碼的話也會發現這幾種創建線程池的方法也都是通過調用ThreadPoolExecutor來實現的。

ThreadPoolExecutor一共有四個構造函數,七個可配參數,分別是

  1. corePoolSize: 線程池中保持存活線程的數量。
  2. maximumPoolSize: 線程池中允許線程數量的最大值
  3. keepAliveTime: 表示線程沒有任務執行時最多保持多久時間會終止
  4. unit: 參數keepAliveTime的時間單位
  5. workQueue: 一個阻塞隊列,用來存儲等待執行的任務
  6. threadFactory: 線程工廠,主要用來創建線程
  7. handler:表示當拒絕處理任務時的策略

分析

那麼Executors到底會導致什麼問題,才會讓開發手冊中直接被定義為不允許了呢。首先就是一個血淋淋的教訓,直接導致線上服務不可用,已經可以算是事故了。

實驗

我們也可以現在我們本地進行一下小實驗:

public class ExecutorsTesting {
    private static ExecutorService executor = Executors.newFixedThreadPool(15);
    public static void main(String[] args) {
        for (int i = 0; i < Integer.MAX_VALUE; i++) {
            executor.execute(new SubThread());
        }
    }
}

class SubThread implements Runnable {
    @Override
    public void run() {
        try {
            Thread.sleep(10000);
        } catch (InterruptedException e) {
            //do nothing
        }
    }
}

運行時指定JVM參數:-Xmx8m -Xms8m,大概幾秒鐘之後,會報出OOM錯誤:

Exception in thread "main" java.lang.OutOfMemoryError: GC overhead limit exceeded
    at com.kaikeba.mybatis.ExecutorsTesting.main(ExecutorsTesting.java:10) 
    //報錯行數為上述代碼中的executor.execute(new SubThread());

那麼為什麼會報出這個錯誤呢。

源碼分析

我們先來看一下Executors中的FixedThreadPool是如何構造的。

public static ExecutorService newFixedThreadPool(int nThreads) {
    return new ThreadPoolExecutor(nThreads, nThreads,
                                  0L, TimeUnit.MILLISECONDS,
                                  new LinkedBlockingQueue<Runnable>());
}

可以看到對於存儲等待執行的任務,FixedThreadPool是通過LinkedBlockingQueue來實現的。而我們知道LinkedBlockingQueue是一個鏈表實現的阻塞隊列,而如果不設置其容量的話,將會是一個無邊界的阻塞隊列,最大長度為Integer.MAX_VALUE由於Executors中並未設置容量,所以應用可以不斷向隊列中添加任務,導致OOM錯誤

上面提到的問題主要體現在newFixedThreadPoolnewSingleThreadExecutor兩個工廠方法上,並不是說newCachedThreadPoolnewScheduledThreadPool這兩個方法就安全了,這兩種方式創建的最大線程數可能是Integer.MAX_VALUE,而創建這麼多線程,必然就有可能導致OOM。

如何該利用ThreadPoolExecutor來創建線程池呢?

我們其實可以看到Executors中的newFixedThreadPool其實也是調用ThreadPoolExecutor來實現的。正如手冊中所說,當我們不用Executors預設創建線程池的方法,而直接自己手動去調用ThreadPoolExecutor,可以讓寫的同學更加明確線程池的運行規則,規避資源耗盡的風險。比如我們在Executors.newFixedThreadPool基礎上給LinkedBlockingQueue加一個容量,當隊列已經滿了,而仍需要添加新的請求會拋出相應異常,我們可以根據異常做相應處理。

public static ExecutorService newFixedThreadPool(int nThreads) {
    return new ThreadPoolExecutor(nThreads, nThreads,
                                  0L, TimeUnit.MILLISECONDS,
                                  new LinkedBlockingQueue<Runnable>(10)); //添加容量大小
}

除了自己定義ThreadPoolExecutor外。還可以利用其它開源類庫,如apache和guava等,可以有更多個性化配置。

參考文章:

https://www.hollischuang.com/archives/2888

https://stackoverflow.com/questions/1094867/when-should-we-use-javas-thread-over-executor#answer-34373289

https://blog.51cto.com/zero01/2306857


本文由博客一文多發平臺 OpenWrite 發佈!

文章首發:https://zhuanlan.zhihu.com/lovebell

個人公眾號:技術Go

您的點贊與支持是作者持續更新的最大動力!


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 命令模式,正如模式的名字一樣,該模式中的不同操作都可以當做不同的命令來執行,可以使用隊列來執行一系列的命令,也可以單獨執行某個命令。該模式重點是將不同的操作封裝為不同的命令對象,將操作的調用者與執行者進行解耦。 命令模式中的Command對象(即每一個命令,或者說命令對象)用於封裝在完成某項操作或觸 ...
  • pip install redis 五大數據類型使用 String類型 List類型 Hash類型 Set類型 Zset類型 全局key操作 ...
  • LinkedList實現原理(JDK1.8) LinkedList底層採用雙向鏈表,如果對鏈表這種結構比較熟悉的話,那LinkedList的實現原理看明白就相當容易。 鏈表通過“指針”將一組零散的記憶體塊串聯起來使用,每一個元素(節點)通過指針指向它的下一個元素,最後一個節點的下一個指向為null,而 ...
  • Java抽象類: 抽象類特點 :抽象類除了不能實例化對象之外,類的其它功能依然存在,成員變數、成員方法和構造方法的訪問方式和普通類一樣。 由於抽象類不能實例化對象,所以抽象類必須被extends [抽象類]方式 繼承才能被使用。 抽象類表示的是一種繼承關係。 (總結就是:抽象類裡面設計跟普通類一樣, ...
  • lua中json和table的互轉,是我們在平時開發過程中經常用到的。比如: 在用lua編寫的伺服器中,如果客戶端發送json格式的數據,那麼在lua處理業務邏輯的時候,必然需要轉換成lua自己的數據結構,如table。此時,就會用到table和json格式的互轉。 在用lua編寫的伺服器中,如果我 ...
  • 先定義消息類型 orders.proto 在GOPATH創建目錄和編譯這個消息類型輸出到該目錄,包名是message 編寫go文件進行序列化和反序列化剛纔生成的包里的類型結構體數據 ...
  • swap(a,b) 用於交換a,b兩個變數的值; max(a,b) 返回a,b中的最大值; min(a,b) 返回a,b中的最小值; abs(x) 返回x的絕對值,x必須是整數; ...
  • ——日拱一卒,不期而至! 你好,我是彤哥,本篇是netty系列的第六篇。 簡介 上一章我們一起學習了Java NIO的核心組件Channel,它可以看作是實體與實體之間的連接,而且需要與Buffer交互,這一章我們就來學習一下Buffer的特性。 概念 Buffer用於與Channel交互時使用,通 ...
一周排行
    -Advertisement-
    Play Games
  • Timer是什麼 Timer 是一種用於創建定期粒度行為的機制。 與標準的 .NET System.Threading.Timer 類相似,Orleans 的 Timer 允許在一段時間後執行特定的操作,或者在特定的時間間隔內重覆執行操作。 它在分散式系統中具有重要作用,特別是在處理需要周期性執行的 ...
  • 前言 相信很多做WPF開發的小伙伴都遇到過表格類的需求,雖然現有的Grid控制項也能實現,但是使用起來的體驗感並不好,比如要實現一個Excel中的表格效果,估計你能想到的第一個方法就是套Border控制項,用這種方法你需要控制每個Border的邊框,並且在一堆Bordr中找到Grid.Row,Grid. ...
  • .NET C#程式啟動閃退,目錄導致的問題 這是第2次踩這個坑了,很小的編程細節,容易忽略,所以寫個博客,分享給大家。 1.第一次坑:是windows 系統把程式運行成服務,找不到配置文件,原因是以服務運行它的工作目錄是在C:\Windows\System32 2.本次坑:WPF桌面程式通過註冊表設 ...
  • 在分散式系統中,數據的持久化是至關重要的一環。 Orleans 7 引入了強大的持久化功能,使得在分散式環境下管理數據變得更加輕鬆和可靠。 本文將介紹什麼是 Orleans 7 的持久化,如何設置它以及相應的代碼示例。 什麼是 Orleans 7 的持久化? Orleans 7 的持久化是指將 Or ...
  • 前言 .NET Feature Management 是一個用於管理應用程式功能的庫,它可以幫助開發人員在應用程式中輕鬆地添加、移除和管理功能。使用 Feature Management,開發人員可以根據不同用戶、環境或其他條件來動態地控制應用程式中的功能。這使得開發人員可以更靈活地管理應用程式的功 ...
  • 在 WPF 應用程式中,拖放操作是實現用戶交互的重要組成部分。通過拖放操作,用戶可以輕鬆地將數據從一個位置移動到另一個位置,或者將控制項從一個容器移動到另一個容器。然而,WPF 中預設的拖放操作可能並不是那麼好用。為瞭解決這個問題,我們可以自定義一個 Panel 來實現更簡單的拖拽操作。 自定義 Pa ...
  • 在實際使用中,由於涉及到不同編程語言之間互相調用,導致C++ 中的OpenCV與C#中的OpenCvSharp 圖像數據在不同編程語言之間難以有效傳遞。在本文中我們將結合OpenCvSharp源碼實現原理,探究兩種數據之間的通信方式。 ...
  • 一、前言 這是一篇搭建許可權管理系統的系列文章。 隨著網路的發展,信息安全對應任何企業來說都越發的重要,而本系列文章將和大家一起一步一步搭建一個全新的許可權管理系統。 說明:由於搭建一個全新的項目過於繁瑣,所有作者將挑選核心代碼和核心思路進行分享。 二、技術選擇 三、開始設計 1、自主搭建vue前端和. ...
  • Csharper中的表達式樹 這節課來瞭解一下表示式樹是什麼? 在C#中,表達式樹是一種數據結構,它可以表示一些代碼塊,如Lambda表達式或查詢表達式。表達式樹使你能夠查看和操作數據,就像你可以查看和操作代碼一樣。它們通常用於創建動態查詢和解析表達式。 一、認識表達式樹 為什麼要這樣說?它和委托有 ...
  • 在使用Django等框架來操作MySQL時,實際上底層還是通過Python來操作的,首先需要安裝一個驅動程式,在Python3中,驅動程式有多種選擇,比如有pymysql以及mysqlclient等。使用pip命令安裝mysqlclient失敗應如何解決? 安裝的python版本說明 機器同時安裝了 ...