ThreadPoolExecutors工做原理(一)

概要

線程池主要解決兩個問題:當執行多個異步任務時,能夠實驗線程池來提升性能,由於使用線程池能夠減小了每一個任務的調用開銷,而且提供了限制和管理資源的方式,例如線程資源。當線程池執行一個任務集合時,它也會持有一些基本的統計數據,例如完成任務的數量。爲了可以在普遍的環境中可用,這個類提供了不少可調整的參數和一些可擴展的鉤子。然而程序員都更加喜歡使用一些工廠方法:Executors#newCachedThreadPool(無界線程池而且線程可自動回收)、Executors#newFixedThreadPool(固定大小的線程池)、Executors#newSingleThreadExecutor(單個後臺線程),這些工廠已經預配置的最經常使用的場景,你也能夠手動的按照以下的指南配置和調整這個類:程序員

核心(corePoolSize)和最大線程池數(maximumPoolSize):
ThreadPoolExecutor能夠根據核心線程數和最大線程數自動調整池的大小。

當調用方法execute(Runable)提交一個新的任務時:安全

  • 若是正在運行的線程數小於corePoolSize:

    建立一個新的線程處理請求,即便其餘的工做線程處於空閒狀態。服務器

  • 若是正在運行的線程數大於corePoolSize可是小於maximumPoolSize:

    只有隊列滿時纔會從新建立一個新的線程。併發

經過設置corePoolSize=maximumPoolSize,你能夠建立一個大小固定的線程池。異步

經過設置maximumPoolSize爲一個無窮大數值(例如Integer.MAX_VALUE),那麼說明你配置的線程池能夠容納任意數量的併發任務。性能

通常狀況下,corePoolSize和maximumPoolSize都會在建立的時候指定的,可是大家也能夠經過調用setCorePoolSize()setMaximumPoolSize()來動態的調整這兩個值。操作系統

按需建立:
默認狀況下,核心線程只有當任務到達時纔會進行建立和開啓,可是能夠重寫方法`prestartCoreThread`或者`prestartAllCoreThreads`改變這個行爲。若是你構建的線程池帶有一個非空的隊列,你可能須要提早開啓一些線程。
  • 建立新的線程:

    新的線程是經過使用ThreadFactory來建立的,若是沒有指定的話,就會使用默認的Executors#defaultThreadFactory,這個默認的工廠建立出的線程都具備相同的ThreadGroup和相同的優先級而且都不是後臺線程。經過實現一個不一樣的線程工廠,你能夠修改線程的名字、線程組、優先級、後臺狀態等等。若是ThreadFactory在從newThread中返回null時未能建立線程,則執行程序將繼續,但可能沒法執行任何任務。線程應該具備修改線程的權限。若是工做線程或者其餘線程使用線程池是不具備這個權限,服務能夠會被降級:配置改變可能沒法及時生效,and a shutdown pool may remain in a* state in which termination is possible but not completed。.net

Keep-alive時間:
若是線程池有超過corePoolSize數的線程數,若是這些過量的線程空閒時間超過`keepAliveTime`將會被終止。當線程池沒有被使用充分時,這種機制能夠下降資源的消耗。當線程池以後又變得活躍起來,新的線程又會被建立。這個參數能夠被動態的改變,使用`setKeepAliveTime(long,TimeUnit)`。經過使用`Integer.MAX_VALUE`能夠有效的禁用此功能。默認狀況下,只有當前的線程數大於corePoolSize,這個策略纔會生效。可是方法`allowCoreThreadTimeOut(boolean)`也可以將核心線程使用這種策略,只要`keepAliveTime`非0便可。
隊列:
任何`BlockingQueue`均可以用來傳輸和保存提交的任務,此隊列的使用和線程池大小有以下的交互:
    • 若是運行的線程數量小於corePoolSize:

      Executor會建立一個新的線程而不是添加到隊列中。線程

    • 若是容許的線程數量大於corePoolSize:

      Executor會將任務添加到隊列中而不是建立一個新的線程。調試

    • 若是一個請求不能添加到隊列中(隊列已滿),若是容許的線程小於maximumPoolSize,將會建立一個新的線程,不然,該任務將會被拒絕。
    • 隊列有三種常見的策略:

      • 直接提交: 它將任務直接提交給線程而不保存它們。在此,若是不存在可用於當即運行任務的線程,則試圖把任務加入隊列將失敗,所以會構造一個新的線程。此策略能夠避免在處理可能具備內部依賴性的請求集時出現鎖。直接提交一般要求無界 maximumPoolSizes 以免拒絕新提交的任務。當命令以超過隊列所能處理的平均數連續到達時,此策略容許無界線程具備增加的可能性。 SynchronousQueue線程安全的Queue,能夠存放若干任務(但當前只容許有且只有一個任務在等待),其中每一個插入操做必須等待另外一個線程的對應移除操做,也就是說A任務進入隊列,B任務必須等A任務被移除以後才能進入隊列,不然執行異常策略。你來一個我扔一個,因此說SynchronousQueue沒有任何內部容量。關於SynchronousQueue:http://blog.csdn.net/yanyan19...
      • 無界隊列:使用一個沒有提早預設容量的無界的隊列,例如:LinkedBlockingQueue,當全部的核心線程處於繁忙時,全部新的任務都會被添加到隊列中。所以若是運行線程數不超過corePoolSize,將會建立一個新的線程(maximumPoolSize這個值將不會再起做用),當每一個任務徹底獨立於其餘任務時,這多是合適的,所以任務不能影響彼此的執行。在一個網頁服務器。雖然這種排隊方式能夠有效地消除瞬時突發請求,可是當命令以比它們能夠被處理的速度更快地平均到達時,可能會致使無限制的工做隊列增加。
      • 有界隊列:有限的隊列(例如,ArrayBlockingQueue)有助於防止與有限的maximumPoolSizes一塊兒使用時的資源耗盡,但可能更難以調整和控制。 隊列大小和最大池大小能夠相互交換:使用大隊列和小池能夠最大限度地減小CPU使用率,操做系統資源和上下文切換開銷,但可能致使人爲的低吞吐量。 若是任務常常阻塞(例如,若是它們是I / O型操做),則系統可能可以安排時間來得到比您容許的更多的線程。 使用小隊列一般須要更大的池大小,這會使CPU更繁忙,但可能會遇到不可接受的調度開銷,這也會下降吞吐量。
    拒絕任務:
    當Executor已經被關閉時,再調用`execute(Runable)`方法添加一個任務時將會被拒絕,當Executor使用有界隊列時,隊列和最大線程數都已經飽和,將會調RejectedExecutionHandler#rejectedExecution(Runnable, ThreadPoolExecutor)方法。提供4種預約義的處理器:

    ThreadPoolExecutor.AbortPolicy:該處理器拋出一個運行時異常RejectedExecutionException。

    ThreadPoolExecutor.CallerRunsPolicy:使用調用者本身的線程運行任務。 提供了一個簡單的反饋控制機制,能夠減慢提交新任務的速度。

    ThreadPoolExecutor.DiscardPolicy:丟棄任務。

    ThreadPoolExecutor.DiscardOldestPolicy:若是executor尚未被關閉,隊列頭部的任務將會被丟棄,而且從新執行(可能再一次失敗,可是會重複執行)

    你能夠定義並使用其餘的實現自RejectedExecutionHandler的類。要作到這一點須要特別注意,特別是在策略僅在特定能力或排隊政策下工做的狀況下。

    鉤子方法:
    這個類提供一寫被protected修飾的方法:

    beforeExecute(Thread, Runnable),afterExecute(Runnable, Throwable)

    這些方法會在每一個任務執行以前和執行以後被調用,這些能夠用來操做執行環境;例如:從新初始化ThreadLocals,收集統計數據,或者添加log。另外,terminated也能夠被重寫當執行程序徹底終止後須要執行的特殊處理。

    若是鉤子或者callback方法拋出異常,內部工做線程可能會失敗並忽然終止。

    維護隊列:
    方法`getQueue()`運行訪問工做隊列以此來進行監控和調試。強烈建議不要將這種方法用於任何其餘目的。當大量的排隊的任務被取消時,兩個提供的方法`remove()`和`purge()`可用來幫助存儲回收。
    回收:
    當線程池在程序中不在被引用而且再也不持有線程,將會自動關閉。若是你但願確保即便用戶忘記調用`shutdown()`也能夠回收未引用的線程池,那麼必須設置適當的保持活動的時間,使用0核心線程的下限來安排未使用的線程最終死亡或設置`allowCoreThreadTimeOut(boolean)`

    問題?

    • 隊列中的三種策略中,直接提交的工做原理是怎麼樣的?
    • 如何擴展ThreadPoolExecutors?
    • 線程池的工做原理是怎麼樣的?如何提交一個任務?如何處理一個任務?
    • 建立一個線程的流程?
    相關文章
    相關標籤/搜索