線程池總結

来源:https://www.cnblogs.com/banjinbaijiu/archive/2018/05/11/9022855.html
-Advertisement-
Play Games

ref:https://www.cnblogs.com/dongguacai/p/6030187.html http://www.360doc.com/content/15/0511/14/12726874_469670444.shtml https://blog.csdn.net/honghail ...


ref:https://www.cnblogs.com/dongguacai/p/6030187.html

     http://www.360doc.com/content/15/0511/14/12726874_469670444.shtml

     https://blog.csdn.net/honghailiang888/article/details/51690711

     http://www.open-open.com/lib/view/open1406778349171.html

   https://blog.csdn.net/chaofanwei2/article/details/51393794

     http://www.360doc.com/content/15/0511/14/12726874_469670444.shtml

什麼是線程池?

  諸如web伺服器、資料庫伺服器、文件伺服器和郵件伺服器等許多伺服器應用都面向處理來自某些遠程來源的大量短小的任務。構建伺服器應用程式的一個過於簡單的模型是:每當一個請求到達就創建一個新的服務對象,然後在新的服務對象中為請求服務。但當有大量請求併發訪問時,伺服器不斷的創建和銷毀對象的開銷很大。所以提高伺服器效率的一個手段就是儘可能減少創建和銷毀對象的次數,特別是一些很耗資源的對象創建和銷毀,這樣就引入了“池”的概念,“池”的概念使得人們可以定製一定量的資源,然後對這些資源進行復用,而不是頻繁的創建和銷毀。

  線程池是預先創建線程的一種技術。線程池在還沒有任務到來之前,創建一定數量的線程,放入空閑隊列中。這些線程都是處於睡眠狀態,即均為啟動,不消耗CPU,而只是占用較小的記憶體空間。當請求到來之後,緩衝池給這次請求分配一個空閑線程,把請求傳入此線程中運行,進行處理。當預先創建的線程都處於運行狀態,即預製線程不夠,線程池可以自由創建一定數量的新線程,用於處理更多的請求。當系統比較閑的時候,也可以通過移除一部分一直處於停用狀態的線程。

線程池簡介    

  多線程技術主要解決處理器單元內多個線程執行的問題,它可以顯著減少處理器單元的閑置時間,增加處理器單元的吞吐能力。       

  假設一個伺服器完成一項任務所需時間為:T1 創建線程時間,T2 線上程中執行任務的時間,T3 銷毀線程時間。
  如果:T1 + T3 遠大於 T2,則可以採用線程池,以提高伺服器性能。
                一個線程池包括以下四個基本組成部分:
                1、線程池管理器(ThreadPool):用於創建並管理線程池,包括創建線程池,銷毀線程池,添加新任務;
                2、工作線程(PoolWorker):線程池中線程,在沒有任務時處於等待狀態,可以迴圈的執行任務;
                3、任務介面(Task):每個任務必須實現的介面,以供工作線程調度任務的執行,它主要規定了任務的入口,任務執行完後的收尾工作,任務的執行狀態等;
                4、任務隊列(taskQueue):用於存放沒有處理的任務。提供一種緩衝機制。                
  線程池技術正是關註如何縮短或調整T1,T3時間的技術,從而提高伺服器程式性能的。它把T1,T3分別安排在伺服器程式的啟動和結束的時間段或者一些空閑的時間段,這樣在伺服器程式處理客戶請求時,不會有T1,T3的開銷了。
  線程池不僅調整T1,T3產生的時間段,而且它還顯著減少了創建線程的數目,看一個例子:
  假設一個伺服器一天要處理50000個請求,並且每個請求需要一個單獨的線程完成。線上程池中,線程數一般是固定的,所以產生線程總數不會超過線程池中線程的數目,而如果伺服器不利用線程池來處理這些請求則線程總數為50000。一般線程池大小是遠小於50000。所以利用線程池的伺服器程式不會為了創建50000而在處理請求時浪費時間,從而提高效率。

  代碼實現中並沒有實現任務介面,而是把Runnable對象加入到線程池管理器(ThreadPool),然後剩下的事情就由線程池管理器(ThreadPool)來完成了。

線程池的創建

複製代碼
1 public ThreadPoolExecutor(int corePoolSize,
2                               int maximumPoolSize,
3                               long keepAliveTime,
4                               TimeUnit unit,
5                               BlockingQueue<Runnable> workQueue,
6                               RejectedExecutionHandler handler) 
複製代碼

  corePoolSize:線程池核心線程數量

  maximumPoolSize:線程池最大線程數量

  keepAliverTime:當活躍線程數大於核心線程數時,空閑的多餘線程最大存活時間

  unit:存活時間的單位

  workQueue:存放任務的隊列

  handler:超出線程範圍和隊列容量的任務的處理程式

線程池的實現原理

  提交一個任務到線程池中,線程池的處理流程如下:

  1、判斷線程池裡的核心線程是否都在執行任務,如果不是(核心線程空閑或者還有核心線程沒有被創建)則創建一個新的工作線程來執行任務。如果核心線程都在執行任務,則進入下個流程。

  2、線程池判斷工作隊列是否已滿,如果工作隊列沒有滿,則將新提交的任務存儲在這個工作隊列里。如果工作隊列滿了,則進入下個流程。

  3、判斷線程池裡的線程是否都處於工作狀態,如果沒有,則創建一個新的工作線程來執行任務。如果已經滿了,則交給飽和策略來處理這個任務。

圖1 線程池的主要處理流程

java類庫中提供的線程池簡介

  java提供的線程池更加強大,相信理解線程池的工作原理,看類庫中的線程池就不會感到陌生了。

圖2 JDK類庫中的線程池的類框圖 

圖3 Executors類的生成ExecutorService實例的靜態方法

  Java通過Executors提供四種線程池,分別為:
  newCachedThreadPool創建一個可緩存線程池,如果線程池長度超過處理需要,可靈活回收空閑線程,若無可回收,則新建線程。
  newFixedThreadPool 創建一個定長線程池,可控制線程最大併發數,超出的線程會在隊列中等待。
  newScheduledThreadPool 創建一個定長線程池,支持定時及周期性任務執行。
  newSingleThreadExecutor 創建一個單線程化的線程池,它只會用唯一的工作線程來執行任務,保證所有任務按照指定順序(FIFO, LIFO, 優先順序)執行。

  (1) newCachedThreadPool
  創建一個可緩存線程池,如果線程池長度超過處理需要,可靈活回收空閑線程,若無可回收,則新建線程。示例代碼如下:

  Java代碼  
    1.   package test;  
    2.   import java.util.concurrent.ExecutorService;  
    3.   import java.util.concurrent.Executors;  
    4.   public class ThreadPoolExecutorTest {  
    5.    public static void main(String[] args) {  
    6.     ExecutorService cachedThreadPool = Executors.newCachedThreadPool();  
    7.     for (int i = 0; i < 10; i++) {  
    8.      final int index = i;  
    9.      try {  
    10.       Thread.sleep(index * 1000);  
    11.      } catch (InterruptedException e) {  
    12.       e.printStackTrace();  
    13.      }  
    14.      cachedThreadPool.execute(new Runnable() {  
    15.       public void run() {  
    16.        System.out.println(index);  
    17.       }  
    18.      });  
    19.     }  
    20.    }  
    21.   }  

  線程池為無限大,當執行第二個任務時第一個任務已經完成,會復用執行第一個任務的線程,而不用每次新建線程。

  (2) newFixedThreadPool
  創建一個定長線程池,可控制線程最大併發數,超出的線程會在隊列中等待。示例代碼如下:

  Java代碼  
      1.   package test;  
      2.   import java.util.concurrent.ExecutorService;  
      3.   import java.util.concurrent.Executors;  
      4.   public class ThreadPoolExecutorTest {  
      5. public static void main(String[] args) {  
      6.  ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);  
      7. for (int i = 0; i < 10; i++) {  
      8. final int index = i;  
      9.   fixedThreadPool.execute(new Runnable() {  
      10. public void run() {  
      11. try {  
      12.   System.out.println(index);  
      13.   Thread.sleep(2000);  
      14.  } catch (InterruptedException e) {  
      15.    e.printStackTrace();  
      16.    }  
      17.   }  
      18.   });  
      19.  }  
      20.  }  
      21. }  

  因為線程池大小為3,每個任務輸出index後sleep 2秒,所以每兩秒列印3個數字。
  定長線程池的大小最好根據系統資源進行設置。如Runtime.getRuntime().availableProcessors()。

  (3)  newScheduledThreadPool
  創建一個定長線程池,支持定時及周期性任務執行。延遲執行示例代碼如下:

  Java代碼  
      1.   package test;  
      2.   import java.util.concurrent.Executors;  
      3.   import java.util.concurrent.ScheduledExecutorService;  
      4.   import java.util.concurrent.TimeUnit;  
      5.   public class ThreadPoolExecutorTest {  
      6. public static void main(String[] args) {  
      7.  ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);  
      8. scheduledThreadPool.schedule(new Runnable() {  
      9. public void run() {  
      10.   System.out.println("delay 3 seconds");  
      11.  }  
      12. }, 3, TimeUnit.SECONDS);  
      13.  }  
      14. }  

  表示延遲1秒後每3秒執行一次。

  (4) newSingleThreadExecutor
  創建一個單線程化的線程池,它只會用唯一的工作線程來執行任務,保證所有任務按照指定順序(FIFO, LIFO, 優先順序)執行。示例代碼如下:

  Java代碼  
      1.   package test;  
      2.   import java.util.concurrent.ExecutorService;  
      3.   import java.util.concurrent.Executors;  
      4.   public class ThreadPoolExecutorTest {  
      5.    public static void main(String[] args) {  
      6.     ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();  
      7.     for (int i = 0; i < 10; i++) {  
      8.      final int index = i;  
      9.      singleThreadExecutor.execute(new Runnable() {  
      10.       public void run() {  
      11.        try {  
      12.         System.out.println(index);  
      13.         Thread.sleep(2000);  
      14.        } catch (InterruptedException e) {  
      15.         e.printStackTrace();  
      16.        }  
      17.       }  
      18.      });  
      19.     }  
      20.    }  
      21.   }  

  結果依次輸出,相當於順序執行各個任務。

線程池技術要點

  從內部實現上看,線程池技術可主要劃分為如下6個要點實現:

圖4 線程池技術要點圖

  •  工作者線程worker:即線程池中可以重覆利用起來執行任務的線程,一個worker的生命周期內會不停的處理多個業務job。線程池“復用”的本質就是復用一個worker去處理多個job,“流控“的本質就是通過對worker數量的控制實現併發數的控制。通過設置不同的參數來控制 worker的數量可以實現線程池的容量伸縮從而實現複雜的業務需求。
  • 待處理工作job的存儲隊列:工作者線程workers的數量是有限的,同一時間最多只能處理最多workers數量個job。對於來不及處理的job需要保存到等待隊列里,空閑的工作者work會不停的讀取空閑隊列里的job進行處理。基於不同的隊列實現,可以擴展出多種功能的線程池,如定製隊列出隊順序實現帶處理優先順序的線程池、定製隊列為阻塞有界隊列實現可阻塞能力的線程池等。流控一方面通過控制worker數控制併發數和處理能力,一方面可基於隊列控制線程池處理能力的上限。
  • 線程池初始化:即線程池參數的設定和多個工作者workers的初始化。通常有一開始就初始化指定數量的workers或者有請求時逐步初始化工作者兩種方式。前者線程池啟動初期響應會比較快但造成了空載時的少量性能浪費,後者是基於請求量靈活擴容但犧牲了線程池啟動初期性能達不到最優。
  • 處理業務job演算法:業務給線程池添加任務job時線程池的處理演算法。有的線程池基於演算法識別直接處理job還是增加工作者數處理job或者放入待處理隊列,也有的線程池會直接將job放入待處理隊列,等待工作者worker去取出執行。
  • workers的增減演算法:業務線程數不是持久不變的,有高低峰期。線程池要有自己的演算法根據業務請求頻率高低調節自身工作者workers的 數量來調節線程池大小,從而實現業務高峰期增加工作者數量提高響應速度,而業務低峰期減少工作者數來節省伺服器資源。增加演算法通常基於幾個維度進行:待處 理工作job數、線程池定義的最大最小工作者數、工作者閑置時間。
  • 線程池終止邏輯:應用停止時線程池要有自身的停止邏輯,保證所有job都得到執行或者拋棄。

 線程池的拒絕策略

  池子有對象池如commons pool的GenericObjectPool(通用對象池技術)也有java裡面的線程池ThreadPoolExecutor,但java裡面的線程池引入了一個叫拒絕執行的策略模式,感覺比GenericObjectPool好一點,意思也就是說當池子滿的時候該如何執行還在不斷往裡面添加的一些任務。 

  像GenericObjectPool只提供了,繼續等待和直接返回空的策略。而ThreadPoolExecutor則提供了一個介面,並內置了4中實現策略供用戶分場景使用。
  ThreadPoolExecutor.execute(Runnable command)提供了提交任務的入口,此方法會自動判斷如果池子滿了的話,則會調用拒絕策略來執行此任務,介面為RejectedExecutionHandler,內置的4中策略分別為AbortPolicy、DiscardPolicy、DiscardOldestPolicy、CallerRunsPolicy。

圖5 拒絕策略關係圖

  AbortPolicy

  為java線程池預設的阻塞策略,不執行此任務,而且直接拋出一個運行時異常,切記ThreadPoolExecutor.execute需要try catch,否則程式會直接退出。

  DiscardPolicy

  直接拋棄,任務不執行,空方法

  DiscardOldestPolicy

  從隊列裡面拋棄head的一個任務,並再次execute 此task。

  CallerRunsPolicy

  在調用execute的線程裡面執行此command,會阻塞入口。

  用戶自定義拒絕策略

  實現RejectedExecutionHandler,並自己定義策略模式。

  再次需要註意的是,ThreadPoolExecutor.submit() 函數,此方法內部調用的execute方法,並把execute執行完後的結果給返回,但如果任務並沒有執行的話(被拒絕了),則submit返回的future.get()會一直等到。 

  future 內部其實還是一個runnable,並把command給封裝了下,當command執行完後,future會返回一個值。

簡單線程池的設計

  一個典型的線程池,應該包括如下幾個部分:
  1、線程池管理器(ThreadPool),用於啟動、停用,管理線程池
  2、工作線程(WorkThread),線程池中的線程
  3、請求介面(WorkRequest),創建請求對象,以供工作線程調度任務的執行
  4、請求隊列(RequestQueue),用於存放和提取請求
  5、結果隊列(ResultQueue),用於存儲請求執行後返回的結果

  線程池管理器,通過添加請求的方法(putRequest)向請求隊列(RequestQueue)添加請求,這些請求事先需要實現請求介面,即傳遞工作函數、參數、結果處理函數、以及異常處理函數。之後初始化一定數量的工作線程,這些線程通過輪詢的方式不斷查看請求隊列(RequestQueue),只要有請求存在,則會提取出請求,進行執行。然後,線程池管理器調用方法(poll)查看結果隊列(resultQueue)是否有值,如果有值,則取出,調用結果處理函數執行。通過以上講述,不難發現,這個系統的核心資源在於請求隊列和結果隊列,工作線程通過輪詢requestQueue獲得人物,主線程通過查看結果隊列,獲得執行結果。因此,對這個隊列的設計,要實現線程同步,以及一定阻塞和超時機制的設計,以防止因為不斷輪詢而導致的過多cpu開銷。

圖6 線程池工作模型

線程池的優點

  1、線程是稀缺資源,使用線程池可以減少創建和銷毀線程的次數,每個工作線程都可以重覆使用。

  2、可以根據系統的承受能力,調整線程池中工作線程的數量,防止因為消耗過多記憶體導致伺服器崩潰。

線程池的註意事項

  雖然線程池是構建多線程應用程式的強大機制,但使用它並不是沒有風險的。在使用線程池時需註意線程池大小與性能的關係,註意併發風險、死鎖、資源不足和線程泄漏等問題。

  (1)線程池大小。多線程應用並非線程越多越好,需要根據系統運行的軟硬體環境以及應用本身的特點決定線程池的大小。一般來說,如果代碼結構合理的話,線程數目與CPU 數量相適合即可。如果線程運行時可能出現阻塞現象,可相應增加池的大小;如有必要可採用自適應演算法來動態調整線程池的大小,以提高CPU 的有效利用率和系統的整體性能。

  (2)併發錯誤。多線程應用要特別註意併發錯誤,要從邏輯上保證程式的正確性,註意避免死鎖現象的發生。

  (3)線程泄漏。這是線程池應用中一個嚴重的問題,當任務執行完畢而線程沒能返回池中就會發生線程泄漏現象。

 


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

-Advertisement-
Play Games
更多相關文章
  • 我們成功書寫了HelloWorld後,又深入瞭解了main函數,提到過main並非是關鍵字,可什麼又是關鍵字呢?這其實就是這章要研究的內容,本節研究關鍵字與標識符,在標識符中我們也會講解一下Java中的駝峰命名; 1.1 關鍵字 在Hello World中,我們發現其中有很多單詞是固定的,這其實就是 ...
  • 最近也是挺煩的,博客園做為程式員的家園,其實不假。雖然現在寫出的隨筆,看的人少。就當自娛自樂了。煩惱就是矛盾引起的,人很多想法都會被外界環境影響。比如一個思考很久的決定,當事情真發生時,考慮過多,受到外界環境的影響就改變了。 面試真的靠技巧,雖然你很NB,但是面試官不知道,不給你機會,也是白搭。真正 ...
  • JSPs /admin/* BASIC ...
  • 單體應用架構 架構總感覺理我很遠,有時候感覺很迷茫。今天起我把我認識到的三種架構寫出來,一是希望沉澱一下自己所學的東西,二是希望有人能指出我的不足指出,向大家學習。 第一篇 單體應用架構我會總結出單體架構的優缺點,和一般我在經歷過的項目中單體架構所用到的技術,以及我需掌握的知識。 第二篇 垂直應用架 ...
  • 課程是按照真實企業級開發項目流程進行講解,通過學習此課程可以體會到真實的大型大數據項目開發流程,學完此課程可以熟練掌握大數據技術,java web技術,docker虛擬化技術,分散式技術,緩存技術,linux等。 ...
  • 雙擊 勾上藍色保存 ...
  • 驗證客戶端鏈接的合法性 如果你想在分散式系統中實現一個簡單的客戶端鏈接認證功能,又不像SSL那麼複雜, 那麼可以利用hmac+加鹽的方式來實現。 SocketServer是標準庫中的一個高級模塊(python3.x中重命名為socketserver), 它的目標是簡化很多樣板代碼,它們是創建網路客... ...
  • Java開源生鮮電商平臺-商品表的設計(源碼可下載) 任何一個電商,無論是B2C還是B2B的電商,商品表的設計關係到整個系統架構的核心。 1. 商品基本信息表:用單詞:goods做為商品表 2. 商品分類信息表: 說明:商品分類信息表存在父子級關係,採用parent_id來做父類,預設是0表示頂級。 ...
一周排行
    -Advertisement-
    Play Games
  • 示例項目結構 在 Visual Studio 中創建一個 WinForms 應用程式後,項目結構如下所示: MyWinFormsApp/ │ ├───Properties/ │ └───Settings.settings │ ├───bin/ │ ├───Debug/ │ └───Release/ ...
  • [STAThread] 特性用於需要與 COM 組件交互的應用程式,尤其是依賴單線程模型(如 Windows Forms 應用程式)的組件。在 STA 模式下,線程擁有自己的消息迴圈,這對於處理用戶界面和某些 COM 組件是必要的。 [STAThread] static void Main(stri ...
  • 在WinForm中使用全局異常捕獲處理 在WinForm應用程式中,全局異常捕獲是確保程式穩定性的關鍵。通過在Program類的Main方法中設置全局異常處理,可以有效地捕獲並處理未預見的異常,從而避免程式崩潰。 註冊全局異常事件 [STAThread] static void Main() { / ...
  • 前言 給大家推薦一款開源的 Winform 控制項庫,可以幫助我們開發更加美觀、漂亮的 WinForm 界面。 項目介紹 SunnyUI.NET 是一個基於 .NET Framework 4.0+、.NET 6、.NET 7 和 .NET 8 的 WinForm 開源控制項庫,同時也提供了工具類庫、擴展 ...
  • 說明 該文章是屬於OverallAuth2.0系列文章,每周更新一篇該系列文章(從0到1完成系統開發)。 該系統文章,我會儘量說的非常詳細,做到不管新手、老手都能看懂。 說明:OverallAuth2.0 是一個簡單、易懂、功能強大的許可權+可視化流程管理系統。 有興趣的朋友,請關註我吧(*^▽^*) ...
  • 一、下載安裝 1.下載git 必須先下載並安裝git,再TortoiseGit下載安裝 git安裝參考教程:https://blog.csdn.net/mukes/article/details/115693833 2.TortoiseGit下載與安裝 TortoiseGit,Git客戶端,32/6 ...
  • 前言 在項目開發過程中,理解數據結構和演算法如同掌握蓋房子的秘訣。演算法不僅能幫助我們編寫高效、優質的代碼,還能解決項目中遇到的各種難題。 給大家推薦一個支持C#的開源免費、新手友好的數據結構與演算法入門教程:Hello演算法。 項目介紹 《Hello Algo》是一本開源免費、新手友好的數據結構與演算法入門 ...
  • 1.生成單個Proto.bat內容 @rem Copyright 2016, Google Inc. @rem All rights reserved. @rem @rem Redistribution and use in source and binary forms, with or with ...
  • 一:背景 1. 講故事 前段時間有位朋友找到我,說他的窗體程式在客戶這邊出現了卡死,讓我幫忙看下怎麼回事?dump也生成了,既然有dump了那就上 windbg 分析吧。 二:WinDbg 分析 1. 為什麼會卡死 窗體程式的卡死,入口門檻很低,後續往下分析就不一定了,不管怎麼說先用 !clrsta ...
  • 前言 人工智慧時代,人臉識別技術已成為安全驗證、身份識別和用戶交互的關鍵工具。 給大家推薦一款.NET 開源提供了強大的人臉識別 API,工具不僅易於集成,還具備高效處理能力。 本文將介紹一款如何利用這些API,為我們的項目添加智能識別的亮點。 項目介紹 GitHub 上擁有 1.2k 星標的 C# ...