jdk6線程池ThreadPoolExecutor 總結

這裏主要總結池子的管理線程、處理任務的流程,以採用LinkedBlockingQueue  corePoolSize=maxPoolSzie=5  KeepAliveTime=0L 爲例解說:

 

 ThreadPoolExecutor .execute(Runnable),向池中存放任務;隨着execute 到corePoolsize任務時,池初始化啓動了corePoolsize個線程,池子維護這5個線程;這5個線程監控Queue等待take任務。

當corePoolSzie線程就緒後,新進任務直接queue.offer();

       

         /**
         * Main run loop
         */
        public void run() {
            try {
                Runnable task = firstTask;
                firstTask = null;
                while (task != null || (task = getTask()) != null) {
                    runTask(task);
                    task = null;
                }
            } finally {
                workerDone(this);
            }
        }

池中線程,一直處於dotask ...queue.take()...dotask的循環之中。而queue.take()和poll()的區別就是直到取到有效element才返回;

 

 引用別人解說:

所有 BlockingQueue 都可用於傳輸和保持提交的任務。可以使用此隊列與池大小進行交互:

如果運行的線程少於 corePoolSize,則 Executor 始終首選添加新的線程,而不進行排隊。(什麼意思?如果當前運行的線程小於corePoolSize,則任務根本不會存放,添加到queue中,而是直接抄傢伙(thread)開始運行) 如果運行的線程等於或多於 corePoolSize,則 Executor 始終首選將請求加入隊列而不添加新的線程。 如果無法將請求加入隊列,則創建新的線程,除非創建此線程超出 maximumPoolSize,在這種情況下,任務將被拒絕。

排隊有三種通用策略:

  1. 直接提交。工作隊列的默認選項是 SynchronousQueue,它將任務直接提交給線程而不保持它們。在此,如果不存在可用於立即運行任務的線程,則試圖把任務加入隊列將失敗,因此會構造一個新的線程。此策略可以避免在處理可能具有內部依賴性的請求集時出現鎖。直接提交通常要求無界 maximumPoolSizes 以避免拒絕新提交的任務。當命令以超過隊列所能處理的平均數連續到達時,此策略允許無界線程具有增長的可能性。
  2. 無界隊列。使用無界隊列(例如,不具有預定義容量的 LinkedBlockingQueue)將導致在所有 corePoolSize 線程都忙時新任務在隊列中等待。這樣,創建的線程就不會超過 corePoolSize。(因此,maximumPoolSize 的值也就無效了。)當每個任務完全獨立於其他任務,即任務執行互不影響時,適合於使用無界隊列;例如,在 Web 頁服務器中。這種排隊可用於處理瞬態突發請求,當命令以超過隊列所能處理的平均數連續到達時,此策略允許無界線程具有增長的可能性。
  3. 有界隊列。當使用有限的 maximumPoolSizes 時,有界隊列(如 ArrayBlockingQueue)有助於防止資源耗盡,但是可能較難調整和控制。隊列大小和最大池大小可能需要相互折衷:使用大型隊列和小型池可以最大限度地降低 CPU 使用率、操作系統資源和上下文切換開銷,但是可能導致人工降低吞吐量。如果任務頻繁阻塞(例如,如果它們是 I/O 邊界),則系統可能爲超過您許可的更多線程安排時間。使用小型隊列通常要求較大的池大小,CPU 使用率較高,但是可能遇到不可接受的調度開銷,這樣也會降低吞吐量。  

 

例子一:使用直接提交策略,也即SynchronousQueue。

 

首先SynchronousQueue是無界的,也就是說他存數任務的能力是沒有限制的,但是由於該Queue本身的特性在某次添加元素後必須等待其他線程取走後才能繼續添加。在這裏不是核心線程便是新創建的線程,但是我們試想一樣下,下面的場景。

例子二:使用無界隊列策略,即LinkedBlockingQueue

這個就拿newFixedThreadPool來說,根據前文提到的規則:
 寫道
如果運行的線程少於 corePoolSize,則 Executor 始終首選添加新的線程,而不進行排隊。
 那麼當任務繼續增加,會發生什麼呢?
 寫道

 

如果運行的線程等於或多於 corePoolSize,則 Executor 始終首選將請求加入隊列,而不添加新的線程。

 OK,此時任務變加入隊列之中了,那什麼時候纔會添加新線程呢?

 

 寫道
如果無法將請求加入隊列,則創建新的線程,除非創建此線程超出 maximumPoolSize,在這種情況下,任務將被拒絕。

這裏就很有意思了,可能會出現無法加入隊列嗎?不像SynchronousQueue那樣有其自身的特點,對於無界隊列來說,總是可以加入的(資源耗盡,當然另當別論)。換句說,永遠也不會觸發產生新的線程!corePoolSize大小的線程數會一直運行,忙完當前的,就從隊列中拿任務開始運行。所以要防止任務瘋長,比如任務運行的實行比較長,而添加任務的速度遠遠超過處理任務的時間,而且還不斷增加,如果任務內存大一些,不一會兒就爆了,呵呵。

 

總結:

  1. ThreadPoolExecutor的使用還是很有技巧的。
  2. 使用無界queue可能會耗盡系統資源。
  3. 使用有界queue可能不能很好的滿足性能,需要調節線程數和queue大小
  4. 線程數自然也有開銷,所以需要根據不同應用進行調節。
通常來說對於靜態任務可以歸爲:
  1. 數量大,但是執行時間很短
  2. 數量小,但是執行時間較長
  3. 數量又大執行時間又長
  4. 除了以上特點外,任務間還有些內在關係
keepAliveTime

jdk中的解釋是:當線程數大於核心時,此爲終止前多餘的空閒線程等待新任務的最長時間。

有點拗口,其實這個不難理解,在使用了“池”的應用中,大多都有類似的參數需要配置。比如數據庫連接池,DBCP中的maxIdle,minIdle參數。
總結:

keepAliveTime和maximumPoolSize及BlockingQueue的類型均有關係。如果BlockingQueue是無界的,那麼永遠不會觸發maximumPoolSize,自然keepAliveTime也就沒有了意義。

反之,如果核心數較小,有界BlockingQueue數值又較小,同時keepAliveTime又設的很小,如果任務頻繁,那麼系統就會頻繁的申請回收線程。
RejectedExecutionHandler
在ThreadPoolExecutor中已經默認包含了4中策略:
CallerRunsPolicy: 線程調用運行該任務的 execute 本身。
AbortPolicy:處理程序遭到拒絕將拋出運行時 RejectedExecutionException
DiscardPolicy:不能執行的任務將被刪除
DiscardOldestPolicy:如果執行程序尚未關閉,則位於工作隊列頭部的任務將被刪除,然後重試執行程序(如果再次失敗,則重複此過程)
發佈了99 篇原創文章 · 獲贊 0 · 訪問量 4893
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章