併發編程之線程池ThreadPoolExecutor

来源:https://www.cnblogs.com/starry-skys/archive/2020/02/27/12375166.html
-Advertisement-
Play Games

前言 在我們平時自己寫線程的測試demo時,一般都是用new Thread的方式來創建線程。但是,我們知道創建線程對象,就會在記憶體中開闢空間,而線程中的任務執行完畢之後,就會銷毀。 單個線程的話還好,如果線程的併發數量上來之後,就會頻繁的創建和銷毀對象。這樣,勢必會消耗大量的系統資源,進而影響執行效 ...


前言

在我們平時自己寫線程的測試demo時,一般都是用new Thread的方式來創建線程。但是,我們知道創建線程對象,就會在記憶體中開闢空間,而線程中的任務執行完畢之後,就會銷毀。

單個線程的話還好,如果線程的併發數量上來之後,就會頻繁的創建和銷毀對象。這樣,勢必會消耗大量的系統資源,進而影響執行效率。

所以,線程池就應運而生。

線程池ThreadPoolExecutor

可以通過idea先看下線程池的類圖,瞭解一下它的繼承關係和大概結構。

它繼承自AbstractExecutorService類,這是一個抽象類,不過裡邊的方法都是已經實現好的。然後這個類實現了ExecutorService介面,裡邊聲明瞭各種方法,包括關閉線程池,以及線程池是否已經終止等。此介面繼承自父介面Executor,裡邊只聲明瞭一個execute方法。

線程池就是為瞭解決單個線程頻繁的創建和銷毀帶來的性能開銷。同時,可以幫我們自動管理線程。並且不需要每次執行新任務都去創建新的線程,而是重覆利用已有的線程,大大提高任務執行效率。

我們打開 ThreadPoolExecutor的源碼,可以看到總共有四個構造函數。

但是,前三個最終都會調用到最後一個構造函數。我們來看下這個構造函數都有哪些參數。(其實,多看下參數的英文解釋就能明白其中的含義,看來英語對程式員來說是真的重要呀)

//核心構造函數
public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory,
                          RejectedExecutionHandler handler) {
        if (corePoolSize < 0 ||
            maximumPoolSize <= 0 ||
            maximumPoolSize < corePoolSize ||
            keepAliveTime < 0)
            throw new IllegalArgumentException();
        if (workQueue == null || threadFactory == null || handler == null)
            throw new NullPointerException();
        this.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
 }

1)corePoolSize

代表核心線程數。每當新的任務提交過來的時候,線程池就會創建一個核心線程來執行這個任務,即使已經有其他的核心線程處於空閑狀態。 而當需要執行的任務數大於核心線程數時,將不再創建新的核心線程。

其實,我們可以看下JDK提供的官方註釋說明。even if they are idle,就照應上邊的加粗字體。

此外,最後一句話說,除非allowCoreThreadTimeOut 這個參數被設置了值。


什麼意思呢,可以去看下這個參數預設值是false,代表當核心線程在空閑狀態時,即沒有任務在執行,就會一直存活,不會銷毀。而設置為true之後,就會有線程存活時間,即假如設置存活時間60秒,則60秒之後,如果沒有新的可執行任務,則核心線程也會自動銷毀。

2)maximumPoolSize

線程所允許的最大數量。即,當阻塞隊列已滿的時候,並且已經創建的線程數小於最大線程數,則會創建新的線程去執行任務。所以,這個參數只有在阻塞隊列滿的情況下才有意義。因此,對於無界隊列,這個參數將會失去效果。

3)keepAliveTime

代表線程空閑後,保持存活的時間。也就是說,超過一定的時間沒有任務執行,線程就會自動銷毀。

註意,這個參數,是針對大於核心線程數,小於最大線程數的那部分非核心線程來說的。如果是任務數量特別多的情況下,可以適當增加這個參數值的大小。以保證,在下個任務到來之前,此線程不會立即銷毀,從而避免線程的重新創建。

4)unit

這個是描述存活時間的時間單位。可以使用TimeUnit裡邊的枚舉值。

5)workQueue

代表阻塞隊列,存儲所有等待執行的任務。

6)threadFactory

代表用來創建線程的工廠。可以自定義一個工廠,傳參進來。如果不指定的話,就會使用預設工廠(Executors類裡邊的 DefaultThreadFactory)。

可以看到,會給每個線程的名字指定一個有規律的首碼。並且每個線程都設置相同的優先順序(優先順序總共有三個,1、5、10)。優先順序可以理解為,優先順序高的線程被執行的概率會更高,但是不代表優先順序高的線程一定會先執行。

7)handler

這個參數代表,拒絕策略。當阻塞隊列和線程池都滿了,即達到了最大線程數,會用什麼策略來處理。一共有四種策略可供選擇,分別對應四個內部類。

  • AbortPolicy:直接拒絕,並拋出異常,這也是預設的策略。
  • CallerRunsPolicy:直接讓調用execute方法的線程去執行此任務。
  • DiscardOldestPolicy:丟棄最老的未處理的任務,然後重新嘗試執行當前的新任務。
  • DiscardPolicy:直接丟棄當前任務,但是不拋異常。

總結一下線程池的執行過程。

  1. 當線程數量未達到corePoolSize的時候,就會創建新的線程來執行任務。
  2. 當核心線程數已滿,就會把任務放到阻塞隊列。
  3. 當隊列已滿,並且未達到最大線程數,就會新建非核心線程來執行任務。
  4. 當隊列已滿,並且達到了最大線程數,則選擇一種拒絕策略來執行。

線程池常用的一些方法

我們一般用 execute 方法來提交任務給線程池。當線程需要返回值時,可以使用submit 方法。

shutdown方法用來關閉線程池。註意,此時不再接受新提交的任務,但是,會繼續處理正在運行的任務和阻塞隊列裡邊的任務。

shutdownNow也會關閉線程池。但是,它不再接受新任務,並且會嘗試終止正在運行的任務。

用Executors創建線程池

瞭解了線程池工作流程之後,那麼我們怎樣去創建它呢。

Executors類提供了四種常用的方法。可以發現它們最終都調用了線程池的構造方法。都有兩種創建方式,其中一種可以傳自定義的線程工廠。此處,只貼出不帶工廠的方法便於理解。


①newFixedThreadPool

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

創建一個固定大小的線程池。核心線程數和最大線程數相等。當線程數量達到核心線程數時,新任務就會放到阻塞隊列裡邊等待執行。

②newSingleThreadExecutor

public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
}

創建一個核心線程數和最大線程數都是1的線程池。即線程池中只會存在一個正在執行的線程,若線程空閑則執行,否則把任務放到阻塞隊列。

③ newCachedThreadPool

public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
}

創建一個可根據實際情況調整線程個數的線程池。這句話,可以理解為,有多少任務同時進來,就會創建同等數量的線程去執行任務。當然,這是線上程數不能超過Integer最大值的前提下。

當再來一個新任務時,若有空閑線程則執行任務。否則,等線程空閑60秒之後,就會自動回收。

當沒有新任務,就不會創建新的線程。

④newScheduledThreadPool

public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
        return new ScheduledThreadPoolExecutor(corePoolSize);
}
    
public ScheduledThreadPoolExecutor(int corePoolSize) {
        super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
              new DelayedWorkQueue());
}

創建一個可指定核心線程數的線程池。這個線程池可以執行周期性的任務。
如果本文對你有用,歡迎點贊,評論,轉發。

學習是枯燥的,也是有趣的。我是「煙雨星空」,歡迎關註,可第一時間接收文章推送。


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

-Advertisement-
Play Games
更多相關文章
  • 本文介紹瞭如何在微信小程式開發中使用 npm 中包的功能,大大提高微信小程式的開發效率,同時也是微信小程式系列教程的視頻版更新。 微信小程式在發佈之初沒有對 npm 的支持功能,這也是目前很多前端開發人員在熟悉了 npm 生態環境後,對微信小程式詬病的地方。 微信小程式在 2.2.1 版本後增加了對 ...
  • 最近購買了極客時間推出的李運華的課程——《從0開始學架構》,本人通過聽音頻和文字閱讀,整理出相關筆記,目的是方便今後再次閱讀。再次感謝李運華的講解,購買鏈接:從0開始學架構 資深技術專家的實戰架構心法 開篇詞 | 照著做,你也能成為架構師 想成為架構師,夢想是美好的,但道路是曲折的,這應該不是個人天 ...
  • 前言 工作的這些年發現一個比較奇怪的現象就是身邊無論是工作十多年的老兵,還是初級剛入行的程式員,在高談闊論技術和趨勢的時候都是人工智慧,大數據,區塊鏈,各種框架,語言,演算法,AI,BI,CI,DI…… 等等,倒是發現很少有人關註資料庫,不知道是因為資料庫感覺太低端還是太低調,總是不容易被人提起 技術 ...
  • 深度講解23種設計模式,力爭每種設計模式都刨析到底。廢話不多說,開始第一種設計模式 - 單例。 作者已知的單例模式有8種寫法,而每一種寫法,都有自身的優缺點。 1,使用頻率最高的寫法,廢話不多說,直接上代碼 /** * @author xujp * 餓漢式 靜態變數 單例 */ public cla ...
  • 概述 單例模式(SingletonPattern),保證一個類僅有一個實例,並提供一個訪問它的全局訪問點。 單例模式有 3 個特點: 單例類只有一個實例對象; 該單例對象必須由單例類自行創建; 單例類對外提供一個訪問該單例的全局訪問點; 在很多比較大型的程式中,全局變數經常被用到。如果不用全局變數, ...
  • 2000年,博客剛進入中國,卻並不被看好,用戶寥寥無幾。 直到2005年,隨著新浪、搜狐等門戶網站的佈局,博客逐漸在國內興起。 但幾年後,因微博、公眾號等媒介的發展,博客的生存空間受到擠壓,開始走向沒落。 然而,迄今為止,依舊有一批熱衷於創作的人在堅持經營著個人博客。 不少技術大牛和程式員,也更願意 ...
  • 題目描述:給定一個二叉樹,返回它的中序 遍歷。示例: 輸入: [1,null,2,3] 1 \ 2 / 3輸出: [1,3,2] 解法一:遞歸(較簡單) /** * Definition for a binary tree node. * public class TreeNode { * int  ...
  • Java synchronized 關鍵字詳解 前置技能點 進程和線程的概念 線程創建方式 線程的狀態狀態轉換 線程安全的概念 synchronized 關鍵字的幾種用法 1. 修飾非靜態成員方法 2. 修飾靜態成員方法 3. 類鎖代碼塊 4. 對象鎖代碼塊 synchronized 修飾非靜態方法 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...