高併發之——不得不說的線程池與ThreadPoolExecutor類淺析

来源:https://www.cnblogs.com/binghe001/archive/2020/02/23/12354417.html
-Advertisement-
Play Games

一、拋磚引玉 既然Java中支持以多線程的方式來執行相應的任務,但為什麼在JDK1.5中又提供了線程池技術呢?這個問題大家自行腦補,多動腦,肯定沒壞處,哈哈哈。。。 說起Java中的線程池技術,在很多框架和非同步處理中間件中都有涉及,而且性能經受起了長久的考驗。可以這樣說,Java的線程池技術是Jav ...


一、拋磚引玉

既然Java中支持以多線程的方式來執行相應的任務,但為什麼在JDK1.5中又提供了線程池技術呢?這個問題大家自行腦補,多動腦,肯定沒壞處,哈哈哈。。。

說起Java中的線程池技術,在很多框架和非同步處理中間件中都有涉及,而且性能經受起了長久的考驗。可以這樣說,Java的線程池技術是Java最核心的技術之一,在Java的高併發領域中,Java的線程池技術是一個永遠繞不開的話題。既然Java的線程池技術這麼重要(怎麼能說是這麼重要呢?那是相當的重要,那家伙老重要了,哈哈哈),那麼,本文我們就來簡單的說下線程池與ThreadPoolExecutor類。至於線程池中的各個技術細節和ThreadPoolExecutor的底層原理和源碼解析,我們會在【高併發專題】專欄中進行深度解析。

引言:本文是高併發中線程池的開篇之作,就暫時先不深入講解,只是讓大家從整體上認識下線程池中最核心的類之一——ThreadPoolExecutor,關於ThreadPoolExecutor的底層原理和源碼實現,以及線程池中的其他技術細節的底層原理和源碼實現,我們會在【高併發專題】接下來的文章中,進行死磕。

二、Thread直接創建線程的弊端

(1)每次new Thread新建對象,性能差。
(2)線程缺乏統一管理,可能無限制的新建線程,相互競爭,有可能占用過多系統資源導致死機或OOM。
(3)缺少更多的功能,如更多執行、定期執行、線程中斷。
(4)其他弊端,大家自行腦補,多動腦,沒壞處,哈哈。

三、線程池的好處

(1)重用存在的線程,減少對象創建、消亡的開銷,性能佳。
(2)可以有效控制最大併發線程數,提高系統資源利用率,同時可以避免過多資源競爭,避免阻塞。
(3)提供定時執行、定期執行、單線程、併發數控制等功能。
(4)提供支持線程池監控的方法,可對線程池的資源進行實時監控。
(5)其他好處,大家自行腦補,多動腦,沒壞處,哈哈。

四、線程池

1.線程池類結構關係

線程池中的一些介面和類的結構關係如下圖所示。

file

後文會死磕這些介面和類的底層原理和源碼。

2.創建線程池常用的類——Executors

  • Executors.newCachedThreadPool:創建一個可緩存的線程池,如果線程池的大小超過了需要,可以靈活回收空閑線程,如果沒有可回收線程,則新建線程
  • Executors.newFixedThreadPool:創建一個定長的線程池,可以控制線程的最大併發數,超出的線程會在隊列中等待
  • Executors.newScheduledThreadPool:創建一個定長的線程池,支持定時、周期性的任務執行
  • Executors.newSingleThreadExecutor: 創建一個單線程化的線程池,使用一個唯一的工作線程執行任務,保證所有任務按照指定順序(先入先出或者優先順序)執行
  • Executors.newSingleThreadScheduledExecutor:創建一個單線程化的線程池,支持定時、周期性的任務執行
  • Executors.newWorkStealingPool:創建一個具有並行級別的work-stealing線程池

    3.線程池實例的幾種狀態

  • Running:運行狀態,能接收新提交的任務,並且也能處理阻塞隊列中的任務
  • Shutdown: 關閉狀態,不能再接收新提交的任務,但是可以處理阻塞隊列中已經保存的任務,當線程池處於Running狀態時,調用shutdown()方法會使線程池進入該狀態
  • Stop: 不能接收新任務,也不能處理阻塞隊列中已經保存的任務,會中斷正在處理任務的線程,如果線程池處於Running或Shutdown狀態,調用shutdownNow()方法,會使線程池進入該狀態
  • Tidying: 如果所有的任務都已經終止,有效線程數為0(阻塞隊列為空,線程池中的工作線程數量為0),線程池就會進入該狀態。
  • Terminated: 處於Tidying狀態的線程池調用terminated()方法,會使用線程池進入該狀態

註意:不需要對線程池的狀態做特殊的處理,線程池的狀態是線程池內部根據方法自行定義和處理的。

4.合理配置線程的一些建議

(1)CPU密集型任務,就需要儘量壓榨CPU,參考值可以設置為NCPU+1(CPU的數量加1)。
(2)IO密集型任務,參考值可以設置為2*NCPU(CPU數量乘以2)

五、線程池最核心的類之一——ThreadPoolExecutor

1.構造方法

ThreadPoolExecutor參數最多的構造方法如下:

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler rejectHandler) 

其他的構造方法都是調用的這個構造方法來實例化對象,可以說,我們直接分析這個方法之後,其他的構造方法我們也明白是怎麼回事了!接下來,就對此構造方法進行詳細的分析。

註意:為了更加深入的分析ThreadPoolExecutor類的構造方法,會適當調整參數的順序進行解析,以便於大家更能深入的理解ThreadPoolExecutor構造方法中每個參數的作用。

上述構造方法接收如下參數進行初始化:

(1)corePoolSize:核心線程數量。

(2)maximumPoolSize:最大線程數。

(3)workQueue:阻塞隊列,存儲等待執行的任務,很重要,會對線程池運行過程產生重大影響。

其中,上述三個參數的關係如下所示:

  • 如果運行的線程數小於corePoolSize,直接創建新線程處理任務,即使線程池中的其他線程是空閑的。
  • 如果運行的線程數大於等於corePoolSize,並且小於maximumPoolSize,此時,只有當workQueue滿時,才會創建新的線程處理任務。
  • 如果設置的corePoolSize與maximumPoolSize相同,那麼創建的線程池大小是固定的,此時,如果有新任務提交,並且workQueue沒有滿時,就把請求放入到workQueue中,等待空閑的線程,從workQueue中取出任務進行處理。
  • 如果運行的線程數量大於maximumPoolSize,同時,workQueue已經滿了,會通過拒絕策略參數rejectHandler來指定處理策略。

根據上述三個參數的配置,線程池會對任務進行如下處理方式:

當提交一個新的任務到線程池時,線程池會根據當前線程池中正在運行的線程數量來決定該任務的處理方式。處理方式總共有三種:直接切換、使用無限隊列、使用有界隊列。

  • 直接切換常用的隊列就是SynchronousQueue。
  • 使用無限隊列就是使用基於鏈表的隊列,比如:LinkedBlockingQueue,如果使用這種方式,線程池中創建的最大線程數就是corePoolSize,此時maximumPoolSize不會起作用。當線程池中所有的核心線程都是運行狀態時,提交新任務,就會放入等待隊列中。
  • 使用有界隊列使用的是ArrayBlockingQueue,使用這種方式可以將線程池的最大線程數量限製為maximumPoolSize,可以降低資源的消耗。但是,這種方式使得線程池對線程的調度更困難,因為線程池和隊列的容量都是有限的了。

根據上面三個參數,我們可以簡單得出如何降低系統資源消耗的一些措施:

  • 如果想降低系統資源的消耗,包括CPU使用率,操作系統資源的消耗,上下文環境切換的開銷等,可以設置一個較大的隊列容量和較小的線程池容量。這樣,會降低線程處理任務的吞吐量。
  • 如果提交的任務經常發生阻塞,可以考慮調用設置最大線程數的方法,重新設置線程池最大線程數。如果隊列的容量設置的較小,通常需要將線程池的容量設置的大一些,這樣,CPU的使用率會高些。如果線程池的容量設置的過大,併發量就會增加,則需要考慮線程調度的問題,反而可能會降低處理任務的吞吐量。

接下來,我們繼續看ThreadPoolExecutor的構造方法的參數。

(4)keepAliveTime:線程沒有任務執行時最多保持多久時間終止
當線程池中的線程數量大於corePoolSize時,如果此時沒有新的任務提交,核心線程外的線程不會立即銷毀,需要等待,直到等待的時間超過了keepAliveTime就會終止。

(5)unit:keepAliveTime的時間單位

(6)threadFactory:線程工廠,用來創建線程
預設會提供一個預設的工廠來創建線程,當使用預設的工廠來創建線程時,會使新創建的線程具有相同的優先順序,並且是非守護的線程,同時也設置了線程的名稱

(7)rejectHandler:拒絕處理任務時的策略

如果workQueue阻塞隊列滿了,並且沒有空閑的線程池,此時,繼續提交任務,需要採取一種策略來處理這個任務。
線程池總共提供了四種策略:

  • 直接拋出異常,這也是預設的策略。實現類為AbortPolicy。
  • 用調用者所在的線程來執行任務。實現類為CallerRunsPolicy。
  • 丟棄隊列中最靠前的任務並執行當前任務。實現類為DiscardOldestPolicy。
  • 直接丟棄當前任務。實現類為DiscardPolicy。

2.ThreadPoolExecutor提供的啟動和停止任務的方法

(1)execute():提交任務,交給線程池執行
(2)submit():提交任務,能夠返回執行結果 execute+Future
(3)shutdown():關閉線程池,等待任務都執行完
(4)shutdownNow():立即關閉線程池,不等待任務執行完

3.ThreadPoolExecutor提供的適用於監控的方法

(1)getTaskCount():線程池已執行和未執行的任務總數
(2)getCompletedTaskCount():已完成的任務數量
(3)getPoolSize():線程池當前的線程數量
(4)getCorePoolSize():線程池核心線程數
(5)getActiveCount():當前線程池中正在執行任務的線程數量


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

-Advertisement-
Play Games
更多相關文章
  • wget用途介紹 日常測試過程中,我們可以用wget命令,來下載一些資源文件。 wget是一個很好文件下載命令, Linux操作系統下,自帶wget命令。 Windows操作系統下,需要自己去下載並配置環境變數。 Windows版下載地址 "https://wget.en.softonic.com/ ...
  • mediainfo命令介紹 mediainfo.exe(Linux/iMac下是未帶尾碼的mediainfo), 是一款音視頻圖片文件的信息查詢工具, 常用於查看多媒體文件的視頻流信息,音頻流信息,字幕流信息等。 也可以用於查看圖片格式。 Windows版下載地址 "https://mediaare ...
  • 簡介: 正常情況下,HttpSession是通過Servlet 容器創建併進行管理的,創建成功之後都是保存在記憶體中。如果開發者需要對項目進行橫向擴展搭建集群,那麼可以利用一些硬體或者軟體工具來做負載均衡,此時,來自同一用戶的HTTP請求就有可能被分發到不同的實例上去,如何保證各個實例之間Sessio ...
  • ffmpeg命令介紹 ffmpeg.exe(linux/imac一般不帶尾碼,ffmpeg), 是一款音視頻編解碼的命令行工具軟體, 常用於多媒體測試的文件製作與轉碼。 我們常用的:格式工廠,MediaCoder等多媒體格式轉換軟體, 其關鍵技術有一部分是直接或間接依賴ffmpeg.exe的, ff ...
  • Java實例初始化程式 是在執行構造函數代碼之前執行的代碼塊。每當我們創建一個新對象時,這些初始化程式就會運行。 1.實例初始化語法 用 花括弧 創建實例初始化程式塊。對象初始化語句寫在括弧內。 2.Java實例初始化器功能 實例初始化器具有以下功能。 我們可以在一個類中定義 多個初始化器 。 所有 ...
  • Java構造函數 是特殊的方法(沒有返回類型),使您可以在應用程式內部的其他類使用對象之前完全初始化對象狀態。Java中的構造方法是使用 關鍵字調用的。下麵讓我們更深入地瞭解構造函數。 1.什麼是java構造函數 構造函數是一種特殊的方法,類似於(沒有確切的方法)構造,它可以幫助程式員在對象可被應用 ...
  • Java訪問修飾符–public, protected, private and default Java提供了 四個訪問修飾符 來設置類,變數,方法和構造函數的訪問級別,即 public , private , protected 和 default 。這些訪問級別修飾符確定其他類是否可以使用特定 ...
  • 在本 Java OOPs 概念教程中,我們將學習四種主要的面向對象原則 抽象、封裝、繼承和多態性。它們也被稱為面向對象編程範式的四大支柱。 1. _ 抽象 _是在不考慮無關細節的情況下公開實體基本細節的過程,以降低用戶的複雜性。 1. _ 封裝 _是將數據和對數據的操作捆綁到一個實體中的過程。 1. ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...