天天看點

線程池的參數和工作機制

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler)       
  1. corePoolSize: the number of threads to keep in the pool, even if they are idle。保留在池中的線程數量,即使這些線程都是空閑的也不清除。
    線程池中的核心線程數,當送出一個任務時,線程池建立一個新線程執行任務,直到目前線程數等于corePoolSize;
    
    如果目前線程數為corePoolSize,繼續送出的任務被儲存到阻塞隊列中,等待被執行;
    
    如果執行了線程池的prestartAllCoreThreads()方法,線程池會提前建立并啟動所有核心線程。      
  2. maximumPoolSize:最大線程數量。
    線程池中允許的最大線程數。如果目前阻塞隊列滿了,且繼續送出任務,則建立新的線程執行任務,前提是目前線程數小于maximumPoolSize      
  3. keepAliveTime,unit:線程空閑下來之後,多長時間回收。
    線程空閑時的存活時間,即當線程沒有任務執行時,繼續存活的時間。預設情況下,該參數隻線上程數大于corePoolSize時才有用      
  4. workQueue:比如說送出了1000個任務,但最大線程數隻有100個,那麼剩下的900個就放到這個阻塞隊列之中來。
    workQueue必須是BlockingQueue阻塞隊列。當線程池中的線程數超過它的corePoolSize的時候,線程會進入阻塞隊列進行阻塞等待。通過workQueue,線程池實作了阻塞功能。
    一般來說,我們應該盡量使用有界隊列,因為使用無界隊列作為工作隊列會對線程池帶來如下影響。
    1)當線程池中的線程數達到corePoolSize後,新任務将在無界隊列中等待,是以線程池中的線程數不會超過corePoolSize。
    2)由于1,使用無界隊列時maximumPoolSize将是一個無效參數。
    3)由于1和2,使用無界隊列時keepAliveTime将是一個無效參數。
    4)更重要的,使用無界queue可能會耗盡系統資源,有界隊列則有助于防止資源耗盡,同時即使使用有界隊列,也要盡量控制隊列的大小在一個合适的範圍。      
  5. threadFactory:
    建立線程的工廠,通過自定義的線程工廠可以給每個建立的線程設定一個具有識别度的線程名,當然還可以更加自由的對線程做更多的設定,比如設定所有的線程為守護線程。
    Executors靜态工廠裡預設的threadFactory,線程的命名規則是“pool-數字-thread-數字”。      
  6. handler:拒絕政策,如何拒絕超出處理能力之外的任務。提供好的政策:1)直接丢棄最老任務 2)抛異常 3)原線程執行,你行你上 4)丢棄目前任務,當作沒看見。
    線程池的飽和政策,當阻塞隊列滿了,且沒有空閑的工作線程,如果繼續送出任務,必須采取一種政策處理該任務,線程池提供了4種政策:
    (1)AbortPolicy:直接抛出異常,預設政策;
    (2)CallerRunsPolicy:用調用者所在的線程來執行任務;
    (3)DiscardOldestPolicy:丢棄阻塞隊列中靠最前的任務,并執行目前任務;
    (4)DiscardPolicy:直接丢棄任務;
    當然也可以根據應用場景實作RejectedExecutionHandler接口,自定義飽和政策,如記錄日志或持久化存儲不能處理的任務。      

線程池的工作機制

線程池的參數和工作機制
1)如果目前運作的線程少于corePoolSize,則建立新線程來執行任務(注意,執行這一步驟需要擷取全局鎖)。
2)如果運作的線程等于或多于corePoolSize,則将任務加入BlockingQueue。
3)如果無法将任務加入BlockingQueue(隊列已滿),則建立新的線程來處理任務。
4)如果建立新線程将使目前運作的線程超出maximumPoolSize,任務将被拒絕,并調用RejectedExecutionHandler.rejectedExecution()方法。      

送出任務

execute()方法用于送出不需要傳回值的任務,是以無法判斷任務是否被線程池執行成功。

submit()方法用于送出需要傳回值的任務。線程池會傳回一個future類型的對象,通過這個future對象可以判斷任務是否執行成功,并且可以通過future的get()方法來擷取傳回值,get()方法會阻塞目前線程直到任務完成,而使用get(long timeout,TimeUnit unit)方法則會阻塞目前線程一段時間後立即傳回,這時候有可能任務沒有執行完。

關閉線程池

可以通過調用線程池的shutdown或shutdownNow方法來關閉線程池。它們的原理是周遊線程池中的工作線程,然後逐個調用線程的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個數。
優先級不同的任務可以使用優先級隊列PriorityBlockingQueue來處理。它可以讓優先級高的任務先執行。
執行時間不同的任務可以交給不同規模的線程池來處理,或者可以使用優先級隊列,讓執行時間短的任務先執行。
建議使用有界隊列。有界隊列能增加系統的穩定性和預警能力,可以根據需要設大一點兒,比如幾千。
如果當時我們設定成無界隊列,那麼線程池的隊列就會越來越多,有可能會撐滿記憶體,導緻整個系統不可用,而不隻是背景任務出現問題。      

參考資料:https://www.cnblogs.com/thisiswhy/p/12690630.html

https://tech.meituan.com/2020/04/02/java-pooling-pratice-in-meituan.html