Java併發編程實戰-併發調度模式框架

Java併發編程實戰-併發調度模式框架

加油站:抱怨是最沒有營養的一件事.

前言:

選擇串行的方式執行任務,串行處理機制通常無法提高高吞吐率和快速響應性,於是我們可以顯式地爲任務創建線程,爲每一個請求創建一個線程來執行任務,這樣可以實現更高的響應性。
但是這樣會帶來很多問題:

  1. 線程的創建和銷燬開銷非常高
  2. 活躍的線程會消耗系統資源,尤其是內存。如果可運行的線程數量多於可用處理器的數量,那麼有些線程就會閒置。大量空閒的線程會佔用許多內存,給GC帶來很大的壓力,而且大量線程在競爭CPU資源時還會產生其他性能開銷。
  3. 可創建線程的數量存在一個限制。這個限制值受多個平臺制約,包括JVM的啓動參數,Thread構造函數中請求棧的大小以及操作系統的限制。
    總得來說,增加線程可以提高系統的吞吐率但是如果超出了這個範圍,再創建更多的線程只會降低程序的速度,更嚴重會導致奔潰。

正文:

一: 使用Executor框架:

對於線程和任務,任務是一組邏輯工作單元,而線程是使任務異步執行的機制,Executor框架可以將線程和任務協調起來。

Executor基於生產者—消費者模型,它提供了一個標準的方法,將任務的提交和執行過程解耦,用Runnable來表示任務。提交任務的操作相當於生產者,執行任務的線程相當於消費者。
Executor有兩種實現方式:

  1. 每線程每任務:
    在這裏插入圖片描述
    2.一個線程所有任務:
    在這裏插入圖片描述

二:使用線程池:

線程池的優勢:
通過重用現有的線程而不是創建新線程,可以減少創建和銷燬線程的開銷.
當請求到來時,由於線程已經存在,可以減少等待時間,從而提高了響應性.

可以通過調用Executors中的靜態工廠方法之一來創建一個線程池:
常用線程池說明:

  1. newSingleThreadExecutor
    創建一個單線程的線程池。這個線程池只有一個線程在工作,也就是相當於單線程串行執行所有任務。如果這個唯一的線程因爲異常結束,那麼會有一個新的線程來替代它。此線程池保證所有任務的執行順序按照任務的提交順序執行。
    如下圖源碼:
    在這裏插入圖片描述
  2. newFixedThreadPool
    創建固定大小的線程池。每次提交一個任務就創建一個線程,直到線程達到線程池的最大大小。線程池的大小一旦達到最大值就會保持不變,如果某個線程因爲執行異常而結束,那麼線程池會補充一個新線程。
    如下圖源碼:
    在這裏插入圖片描述
  3. newCachedThreadPool
    創建一個可緩存的線程池。如果線程池的大小超過了處理任務所需要的線程,
    那麼就會回收部分空閒(60秒不執行任務)的線程,當任務數增加時,此線程池又可以智能的添加新線程來處理任務。此線程池不會對線程池大小做限制,線程池大小完全依賴於操作系統(或者說JVM)能夠創建的最大線程大小。
    如下圖源碼:
    在這裏插入圖片描述
    4.newScheduledThreadPool
    創建一個大小無限的線程池。此線程池支持定時以及週期性執行任務的需求。
    如下圖源碼:
    在這裏插入圖片描述
    具體參數含義:
    corePoolSize:池中所保存的線程數,包括空閒線程
    maximumPoolSize:線程池允許的最大線程數量
    keepAliveTime:存活時間,當線程數大於核心線程數時,多出來的線程爲空餘線程,當空餘線程在一定的時間內沒有新任務到達執行,則終止該線程
    unit :keepAliveTime 參數的時間單位
    workQueue :執行前用於保持任務的隊列。

三: Executor的生命週期:ExecutorService

從上面所述,我們可以看出Executor通常是創建線程來執行任務,爲了解決執行服務的生命週期問題,Executor擴展了ExecutorService接口,添加了生命週期的管理方法,具體看下圖源碼:
在這裏插入圖片描述
ExecutorService有三種狀態:
running(運行), shuting down(關閉), terminated(已終止)。
shuting down(關閉)狀態:
shutdown:將停止接受新的任務, 同時等待已經提交的任務完成, 包括尚未完成的任務.
terminated(已終止)狀態:
等所有任務都完成之後,進入terminated狀態, 可以調用awaitTermination等待ExecutorService到達終止狀態, 也可以輪詢檢查isTerminated判斷是否終止. 通常shutdown會緊隨awaitTermination之後, 這樣可以產生同步地關閉ExecutorService的效果.

四: 延遲任務和週期任務

延遲任務:在100ms後執行任務.
週期任務:沒100ms執行一次任務.
一般來說,Timer類用於執行延遲任務和週期任務,但是Timer有以下兩個問題:

  1. 只會創建一個線程來執行所有task, 如果一個task非常耗時, 會導致其他的task的實效準確性出問題
  2. Timer線程並不捕獲異常,對於一些未檢查異常(RuntimeException)拋出,Timer線程會被終止

五: 飽和策略

當有界隊列被填滿後,飽和策略就游泳了。ThreadPoolExecutor的飽和策略可以通過調用setRejectedExecutionHandler來修改。JDK提供了幾種不同的實現,每一種實現有不同的飽和策略:
具體說明:
中止策略(AbortPolicy):默認的飽和策略,會拋出RejectedExecutionException: 調用者可以捕獲這個隱藏然後編寫滿足自己需求的處理代碼
拋棄策略(DiscardPolicy):當最新提交的任務不能進入隊列等待執行時, 遺棄(discard)策略會默認放棄這個任務.
遺棄最舊策略(DiscardOldestPolicy):選擇丟棄的任務是本應該接下來就應該執行的任務, 該策略還會嘗試去重新提交新任務。(該策略最好不要和優先級隊列一起使用)
調用者運行策略(CallerRunsPolicy):既不會丟棄哪個任務, 也不會拋出任何異常. 它會把一些任務退回到調用者那裏, 從此緩解新任務流. 他不會在池線程中執行最新提交的任務, 但是他會在一個調用了execute的線程中執行。當工作隊列充滿後, 並沒有預置的飽和策略來阻塞execute,當工作隊列充滿後,並沒有預置的飽和策略來阻塞execute.但是,使用Semaphore信號量可以實現這個效果.

結尾:

隨着日益增長的互聯網需求,現在在各大互聯網公司中,高併發處理已經是一個非常常見的問題,因此對於java程序員來說,系統掌握併發編程的能力與實戰技巧無論對於實際工作應用還是面試來講都很有必要,而線程池作爲併發編程中的基礎第一步,上訴多爲理論知識,後續源碼會上傳,感謝您的關注,關注微信公衆號:十點攀程 ,精彩持續進行中…
在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章