多線程與高併發10-線程池

線程池

其實在真正的開發中,高併發的情形並直接用多線程,而是用線程池技術的地方比較多,線程的池化技術有不少好處,JDK也提供了線程池相關的類,下面將深刻展開進行介紹java

線程池的優勢

  • 下降資源消耗:經過重複利用已建立的線程下降線程建立和銷燬形成的消耗
  • 提升響應速度:當任務到達時,任務能夠不須要等到線程建立就能當即執行nginx

    假設一個服務器完成一項任務所需時間爲:T1 建立線程時間,T2 在線程中執行任務的時間,T3 銷燬線程時間。   若是:T1 + T3 遠大於 T2,則能夠採用線程池,以提升服務器性能。線程池技術正是關注如何縮短或調整T1,T3時間的技術,從而提升服務器程序性能的。它把T1,T3分別安排在服務器程序的啓動和結束的時間段或者一些空閒的時間段,這樣在服務器程序處理客戶請求時,不會有T1,T3的開銷了
  • 提升線程的可管理性:線程是稀缺資源,若是無限制地建立,不只會消耗系統資源,還會下降系統的穩定性,使用線程池能夠進行統一分配、調優和監控
假設一個服務器一天要處理50000個請求,而且每一個請求須要一個單獨的線程完成。在線程池中,線程數通常是固定的,因此產生線程總數不會超過線程池中線程的數目,而若是服務器不利用線程池來處理這些請求則線程總數爲50000。通常線程池大小是遠小於50000。因此利用線程池的服務器程序不會爲了建立50000而在處理請求時浪費時間,從而提升效率

線程池類結構體系

  • Executor是一個接口,它是Executor框架的基礎,它將任務的提交與任務的執行分離開來
  • ExecutorService接口繼承了Executor,在其上作了一些shutdown()submit()的擴展,能夠說是真正的線程池接口
  • AbstractExecutorService抽象類實現了ExecutorService接口中的大部分方法---能夠看到模版方法,鉤子函數是源碼中很是經常使用的設計模式
  • ThreadPoolExecutor是線程池的核心實現類,用來執行被提交的任務
  • ScheduledExecutorService接口繼承了ExecutorService接口,提供了帶"週期執行"功能ExecutorService
  • ScheduledThreadPoolExecutor是一個實現類,能夠在給定的延遲後運行命令,或者按期執行命令。ScheduledThreadPoolExecutor比Timer更靈活,功能更強大

image.png

線程池的七個參數

public ThreadPoolExecutor(int corePoolSize,
   int maximumPoolSize,
   long keepAliveTime,
   TimeUnit unit,
   BlockingQueue<Runnable> workQueue,
   ThreadFactory threadFactory,
   RejectedExecutionHandler handler)
corePoolSize 線程池核心線程數

線程池中的核心線程數,當提交一個任務時,線程池建立一個新線程執行任務,直到當前線程數等於corePoolSize面試

線程池中會維護一個最小的線程數量,即便這些線程處理空閒狀態,他們也不會 被銷燬,除非設置了allowCoreThreadTimeOut。這裏的最小線程數量便是corePoolSize數據庫

若是當前線程數爲corePoolSize,繼續提交的任務被保存到阻塞隊列中,等待被執行小程序

若是執行了線程池的prestartAllCoreThreads()方法,線程池會提早建立並啓動全部核心線程設計模式

maximumPoolSize 線程池最大線程數量

線程池中容許的最大線程數。若是當前阻塞隊列滿了,且繼續提交任務,則建立新的線程執行任務,前提是當前線程數小於maximumPoolSize服務器

keepAliveTime 空閒線程存活時間

線程空閒時的存活時間,即當線程沒有任務執行時,繼續存活的時間。默認狀況下,該參數只在線程數大於corePoolSize時纔有用多線程

TimeUnit 空間線程存活時間單位

keepAliveTime的時間單位併發

workQueue 工做隊列

workQueue必須是BlockingQueue阻塞隊列。當線程池中的線程數超過它的corePoolSize的時候,線程會進入阻塞隊列進行阻塞等待。經過workQueue,線程池實現了阻塞功能框架

用於保存等待執行的任務的阻塞隊列,通常來講,咱們應該儘可能使用有界隊列,由於使用無界隊列做爲工做隊列會對線程池帶來以下影響:

1)當線程池中的線程數達到corePoolSize後,新任務將在無界隊列中等待,所以線程池中的線程數不會超過corePoolSize。

2)因爲1,使用無界隊列時maximumPoolSize將是一個無效參數。

3)因爲1和2,使用無界隊列時keepAliveTime將是一個無效參數。

4)更重要的,使用無界queue可能會耗盡系統資源,有界隊列則有助於防止資源耗盡,同時即便使用有界隊列,也要儘可能控制隊列的大小在一個合適的範圍

因此咱們通常會使用,ArrayBlockingQueue、LinkedBlockingQueue、SynchronousQueue、PriorityBlockingQueue

threadFactory 線程工廠

建立線程的工廠,經過自定義的線程工廠能夠給每一個新建的線程設置一個具備識別度的線程名,固然還能夠更加自由的對線程作更多的設置,好比設置全部的線程爲守護線程

Executors靜態工廠裏默認的threadFactory,線程的命名規則是「pool-數字-thread-數字」

RejectedExecutionHandler 拒絕策略

線程池的飽和策略,當阻塞隊列滿了,且沒有空閒的工做線程,若是繼續提交任務,必須採起一種策略處理該任務,線程池提供了4種策略:

(1)AbortPolicy:直接拋出異常,默認策略

(2)CallerRunsPolicy:用調用者所在的線程來執行任務

(3)DiscardOldestPolicy:丟棄阻塞隊列中靠最前的任務,並執行當前任務

(4)DiscardPolicy:直接丟棄任務

固然也能夠根據應用場景實現RejectedExecutionHandler接口,自定義飽和策略,如記錄日誌或持久化存儲不能處理的任務

擴展線程池

實際上,JDK 的線程池已經爲咱們預留的接口,在線程池核心方法中,有2 個方法是空的,就是給咱們預留的。還有一個線程池退出時會調用的方法。

能夠看到,每一個任務執行先後都會調用 beforeExecuteafterExecute方法。至關於執行了一個切面。而在調用 shutdown 方法後則會調用 terminated 方法

線程池的工做機制

1)若是當前運行的線程少於corePoolSize,則建立新線程來執行任務(注意,執行這一步驟須要獲取全局鎖)

2)若是運行的線程等於或多於corePoolSize,則將任務加入BlockingQueue

3)若是沒法將任務加入BlockingQueue(隊列已滿),則建立新的線程來處理任務

4)若是建立新線程將使當前運行的線程超出maximumPoolSize,任務將被拒絕,並調用RejectedExecutionHandler.rejectedExecution()方法

image.png

若是線程任務執行完,線程空閒數過多,那麼超時參數開始起做用,超過超時時間,則多於 corePoolSize的線程會自動關閉,(keepAliveTime=0,表示當即中止),若是但願 corePoolSize的線程空閒也自動關閉,經過 allowCoreThreadTimeOut接口進行設置

提交任務

  • execute():方法用於提交不須要返回值的任務,因此沒法判斷任務是否被線程池執行成功
  • submit():方法用於提交須要返回值的任務。線程池會返回一個future類型的對象,經過這個future對象能夠判斷任務是否執行成功,而且能夠經過future的get()方法來獲取返回值,get()方法會阻塞當前線程直到任務完成,而使用get(long timeout,TimeUnit unit)方法則會阻塞當前線程一段時間後當即返回,這時候有可能任務沒有執行完

關閉線程池

能夠經過調用線程池的shutdownshutdownNow方法來關閉線程池。它們的原理是遍歷線程池中的工做線程,而後逐個調用線程的interrupt方法來中斷線程,因此沒法響應中斷的任務可能永遠沒法終止。可是它們存在必定的區別,shutdownNow首先將線程池的狀態設置成STOP,而後嘗試中止全部的正在執行或暫停任務的線程,並返回等待執行任務的列表,而shutdown只是將線程池的狀態設置成SHUTDOWN狀態,而後中斷全部沒有正在執行任務的線程

只要調用了這兩個關閉方法中的任意一個,isShutdown方法就會返回true。當全部的任務都已關閉後,才表示線程池關閉成功,這時調用isTerminaed方法會返回true。至於應該調用哪種方法來關閉線程池,應該由提交到線程池的任務特性決定,一般調用shutdown方法來關閉線程池,若是任務不必定要執行完,則能夠調用shutdownNow方法。

合理地配置線程池

要想合理地配置線程池,就必須首先分析任務特性,能夠從如下幾個角度來分析:

  • 任務的性質:CPU密集型任務、IO密集型任務和混合型任務
  • 任務的優先級:高、中和低
  • 任務的執行時間:長、中和短
  • 任務的依賴性:是否依賴其餘系統資源,如數據庫鏈接

性質不一樣的任務能夠用不一樣規模的線程池分開處理

CPU密集型任務應配置儘量小的線程,如配置Ncpu+1個線程的線程池。因爲IO密集型任務線程並非一直在執行任務,則應配置儘量多的線程,如2*Ncpu。

混合型的任務,若是能夠拆分,將其拆分紅一個CPU密集型任務和一個IO密集型任務,只要這兩個任務執行的時間相差不是太大,那麼分解後執行的吞吐量將高於串行執行的吞吐量。若是這兩個任務執行時間相差太大,則不必進行分解。能夠經過Runtime.getRuntime().availableProcessors()方法得到當前設備的CPU個數。

對於IO型的任務的最佳線程數,有個公式能夠計算

Nthreads = NCPU UCPU (1 + W/C)

其中:

  • NCPU是處理器的核的數目
  • UCPU是指望的CPU利用率(該值應該介於0和1之間)
  • W/C是等待時間與計算時間的比率

等待時間與計算時間咱們在Linux下使用相關的vmstat命令或者top命令查看

優先級不一樣的任務可使用優先級隊列PriorityBlockingQueue來處理。它可讓優先級高的任務先執行

執行時間不一樣的任務能夠交給不一樣規模的線程池來處理,或者可使用優先級隊列,讓執行時間短的任務先執行

依賴數據庫鏈接池的任務,由於線程提交SQL後須要等待數據庫返回結果,等待的時間越長,則CPU空閒時間就越長,那麼線程數應該設置得越大,這樣才能更好地利用CPU

建議使用有界隊列。有界隊列能增長系統的穩定性和預警能力,能夠根據須要設大一點兒,好比幾千

假設,咱們如今有一個Web系統,裏面使用了線程池來處理業務,在某些狀況下,系統裏後臺任務線程池的隊列和線程池全滿了,不斷拋出拋棄任務的異常,經過排查發現是數據庫出現了問題,致使執行SQL變得很是緩慢,由於後臺任務線程池裏的任務全是須要向數據庫查詢和插入數據的,因此致使線程池裏的工做線程所有阻塞,任務積壓在線程池裏

若是當時咱們設置成無界隊列,那麼線程池的隊列就會愈來愈多,有可能會撐滿內存,致使整個系統不可用,而不僅是後臺任務出現問題

JDK預約義線程池

FixedThreadPool詳解

線程數固定,且所有是核心線程,Integer.MAX_VALUE長度的BlockingQueue

適用於爲了知足資源管理的需求,而須要限制當前線程數量的應用場景,它適用於負載比較重的服務器

當線程池中的線程數大於corePoolSize時,keepAliveTime爲多餘的空閒線程等待新任務的

最長時間,超過這個時間後多餘的線程將被終止。這裏把keepAliveTime設置爲0L,意味着多餘的空閒線程會被當即終止

FixedThreadPool使用無界隊列LinkedBlockingQueue做爲線程池的工做隊列(隊列的容量爲Integer.MAX_VALUE

SingleThreadExecutor

corePoolSizemaximumPoolSize被設置爲1。其餘參數與FixedThreadPool相同。SingleThreadExecutor使用界無隊列LinkedBlockingQueue做爲線程池的工做隊列(隊列的容量爲Integer.MAX_VALUE

CachedThreadPool

大小爲0的BlockingQueue,能夠起Integer.MAX_VALUE個線程處理,適用於執行不少的短時間異步任務的小程序,或者是負載較輕的服務器

這裏把keepAliveTime設置爲60L,意味着CachedThreadPool中的空閒線程等待新任務的最長時間爲60秒,空閒線程超過60秒後將會被終止

FixedThreadPool和SingleThreadExecutor使用無界隊列LinkedBlockingQueue做爲線程池的工做隊列。CachedThreadPool使用沒有容量的SynchronousQueue做爲線程池的工做隊列,但CachedThreadPool的maximumPool是無界的。這意味着,若是主線程提交任務的速度高於maximumPool中線程處理任務的速度時,CachedThreadPool會不斷建立新線程。極端狀況下,CachedThreadPool會由於建立過多線程而耗盡CPU和內存資源

WorkStealingPool

利用全部運行的處理器數目來建立一個工做竊取的線程池,使用forkJoinPool實現

每一個線程有本身的等待隊列,本身的隊列處理完,會去其餘線程尾端偷取任務來進行處理

ScheduledThreadPoolExecutor ---- DelayedWorkQueue實現

使用工廠類Executors來建立。Executors能夠建立2種類型的ScheduledThreadPoolExecutor,以下:

  • ScheduledThreadPoolExecutor。包含若干個線程的ScheduledThreadPoolExecutor
  • SingleThreadScheduledExecutor。只包含一個線程的ScheduledThreadPoolExecutor

ScheduledThreadPoolExecutor適用於須要多個後臺線程執行週期任務,同時爲了知足資源管理的需求而須要限制後臺線程的數量的應用場景。

SingleThreadScheduledExecutor適用於須要單個後臺線程執行週期任務,同時須要保證順序地執行各個任務的應用場景

quartz定時器框架 面試:假如提供一個鬧鐘服務,訂閱這個服務的人特別多,10億人,怎麼優化?nginx把請求分到不一樣服務器上,各個服務器上再用線程池+消息隊列消費
相關文章
相關標籤/搜索