1. 程式人生 > >(八)如何理解Executors框架下的四大執行緒池執行原理和適用場景

(八)如何理解Executors框架下的四大執行緒池執行原理和適用場景

(一)由標準執行緒池ThreadPoolExecutor演變而來的四大執行緒池

(1)FixedThreadPool是一種可重用固定執行緒數的執行緒池,阻塞佇列適用的是LinkedBlockingQueue的無界阻塞佇列,適用於需要保證所有提交的任務都要被執行的情況

(2)SingleThreadExecutor是使用單個worker執行緒的Executor,也就是他的corePoolSize和maximumPoolSize都被設定為1,而且阻塞佇列適用的也是無界限的LinkedBlockingQueue,適用於適用於業務邏輯上只允許1個執行緒進行處理的場景

(3)CachedThreadPool是一個會根據需要建立新執行緒的執行緒池,corePoolSize被設定為0,即corePool為空;maximumPoolSize被設定為Integer.MAX_VALUE,即maximumPool是無界的,適用於如果希望提交的任務儘快分配執行緒執行,就可以適用這個

(4)ScheduledThreadPoolExecutor會把待排程的任務(ScheduledFutureTask)
放到一個DelayQueue中,而且DelayQueue是一個在PriorityQueu基礎上封裝之後的一個阻塞佇列,然後執行緒池會先根據誰的執行緒任務先到期,然後會先去執行那個任務,如果是兩個任務都是同時到期的話,會再去根據任務序號去判斷先去執行哪個定時任務

(5)當然除此之外,我們還可以根據標準執行緒池ThreadPoolExecutor的引數來進行自定義的設定針對不同的業務來進行個性化的設計,前提是我們要十分了解執行緒池的幾個引數

(1)FixedThreadPool的實現原理

在這裡插入圖片描述

  1. 如果當有一個執行緒被提交執行的時候,就會先看corePool執行緒池的是不是已經滿了,如果沒有滿的時候就會直接建立一個執行緒來執行任務
  2. 如果corePool核心執行緒池已經滿的時候,此時只能把被提交的執行緒放到LinkedBlockingQueue這個無界阻塞佇列中等待
  3. 如果corePool執行緒池中的執行緒要是有完成的任務之後,就會繼續向LinkedBlockingQueue佇列中去取排在前面的執行緒去執行任務,然後就會一直反覆迴圈這個從第一步到第三步這個過程
    如果使用這個執行緒池會出現的一些缺點:
  4. 當執行緒池中的執行緒數達到corePoolSize後,新任務將在無界佇列中等待,因此執行緒池中的執行緒數不會超過corePoolSize。
  5. 無界佇列時maximumPoolSize將是一個無效引數
  6. 使用無界佇列時keepAliveTime將是一個無效引數
  7. 由於使用無界佇列,執行中的FixedThreadPool(未執行方法shutdown()或
    shutdownNow())不會拒絕任務(不會呼叫RejectedExecutionHandler.rejectedExecution方法)。

(2)SingleThreadExecutor實現原理的詳解

在這裡插入圖片描述

SingleThreadExecutor的corePoolSize和maximumPoolSize被設定為1。也就是說整個SingleThreadExecutor執行緒池中只會保證一直有一個執行緒線上程池中在執行,他用的也是LinkedBlockingQueue無界佇列,他的缺點和上一個固定執行緒大小的執行緒池的缺點是一樣的

  1. 也就是主執行緒如果有提交一個新的執行緒過來的話,他會先去看看SingleThreadExecutor執行緒池是不是有一個執行緒正再執行,如果沒有一個執行緒的話,就會去直接執行剛剛提交過來的這個執行緒
  2. 如果已經有一個執行緒正在執行的話,就會吧這個執行緒放到無節限的對列中去執行
  3. 當corePool這個核心執行緒池中的執行緒執行完畢之後,他就會去從哪個LinkedBlockingQueue無界限對列中去取到一個執行緒繼續來執行,然後依次反覆執行

(3)CachedThreadPool實現原理的詳解

在這裡插入圖片描述

這裡先來了解一下行阻塞佇列SynchronousQueue是個什麼玩意?

  1. 首先SynchronousQueue沒有容量。與其他BlockingQueue不同,SynchronousQueue是一個不儲存元素的BlockingQueue。每一個put操作必須要等待一個take操作,否則不能繼續新增元素,反之亦然。
  2. SynchronousQueue分為公平和非公平,預設情況下采用非公平性訪問策略,當然也可以通過建構函式來設定為公平性訪問策略(為true即可)
  3. 因為沒有容量,所以對應 peek, contains, clear, isEmpty … 等方法其實是無效的。例如clear是不執行任何操作的,contains始終返回false,peek始終返回null。

下面說一下他的實現原理的流程:

  1. 首先主執行緒會先執行excute()方法,把這個提交的待執行的執行緒放到SynchronousQueue這個阻塞佇列中(他就好比像一箇中間傳遞員,如果A【代表主執行緒】給SynchronousQueue一個包裹,但是SynchronousQueue這裡不能存放包裹公司規定)此時SynchronousQueue會等待著B【也就是來取包裹的使用者】來取包裹,此時只有當B去問SynchronousQueue說有沒有人給我包裹啊?如果有趕緊給我,我只能等待keepAliveTime【自己定義的時間】這麼久的時間啊,不然我就走了)
  2. 如果最開始的時候當maximumPool中沒有有空閒執行緒的時候,當然這樣是永遠都不會有人向SynchronousQueue詢問有沒有提交的執行緒過來,這種情況下就會失敗,此時CachedThreadPool會建立一個新執行緒執行任務。
  3. 當第二步中已經建立好了一個新的空閒執行緒的時候,此時會再去向SynchronousQueue呼叫poll()方法去問SynchronousQueue有沒有主執行緒提供任務分配給我,如果有就直接給他執行了,如果沒有的話,剛剛新建立好的一個執行緒會在定義的引數時間後銷燬,然後迴圈往復進行。
    在這裡插入圖片描述

(4)ScheduledThreadPoolExecutor定時執行緒池的工作原理

  1. 先來了解一下ScheduledThreadPoolExecutor的適用場景和JDK提供的Timer

首先Timer簡單易用,但所有任務都是由同一個執行緒來排程,任務序列執行,任務之間存在互相干擾,一是前一個任務的延遲會導致後面的任務延遲,二是前一個任務異常導致後面的任務不再執行,三是Timer執行週期任務時依賴系統時間,如果當前系統時間發生變化,執行行為也會出現變化
這個是Timer和ScheduledThreadPoolExecutor的對比圖
這是點

ScheduledThreadPoolExecutor的執行原理圖
在這裡插入圖片描述
在這裡插入圖片描述

  1. 首先主執行緒會先提交過來一個定時排程任務提交到DelayQueue這個阻塞佇列中,如果ScheduledThreadPoolExecutor執行緒池中有空閒的執行緒就會去執行個ScheduledFutureTask裡面的到期任務
  2. 在執行完事之後,執行緒1修改ScheduledFutureTask的time變數為下次將要被執行的時間
  3. 執行緒1把這個修改time之後的ScheduledFutureTask放回DelayQueue中(DelayQueue.add())。