還不知道線程池的好處?快來了解一下

語言: CN / TW / HK
摘要:線程池的好處:重用存在的線程,減少對象創建、消亡的開銷,性能佳;可以有效控制最大併發線程數,提高系統資源利用率,同時可以避免過多資源競爭,避免阻塞。

本文分享自華為雲社區《【高併發】線程池介紹》,作者: 冰 河 。

1.new Thread弊端

(1)每次new Thread新建對象,性能差。

(2)線程缺乏統一管理,可能無限制的新建線程,相互競爭,有可能佔用過多系統資源導致死機或OOM。

(3)缺少更多的功能,如更多執行、定期執行、線程中斷。

2.線程池的好處

(1)重用存在的線程,減少對象創建、消亡的開銷,性能佳。

(2)可以有效控制最大併發線程數,提高系統資源利用率,同時可以避免過多資源競爭,避免阻塞。

(3)提供定時執行、定期執行、單線程、併發數控制等功能。

線程池 - ThreadPoolExecutor

ThreadPoolExecutor參數最多的構造方法如下:

public ThreadPoolExecutor(int corePoolSize,
 int maximumPoolSize,
 long keepAliveTime,
 TimeUnit unit,
 BlockingQueue<Runnable> workQueue,
 ThreadFactory threadFactory,
 RejectedExecutionHandler rejectHandler) 

接收如下參數進行初始化:

(1)corePoolSize:核心線程數量。
(2)maximumPoolSize:最大線程數。
(3)workQueue:阻塞隊列,存儲等待執行的任務,很重要,會對線程池運行過程產生重大影響。

三個參數的關係如下:

如果運行的線程數小於corePoolSize,直接創建新線程處理任務,即使線程池中的其他線程是空閒的。

如果運行的線程數大於等於corePoolSize,並且小於maximumPoolSize,此時,只有當workQueue滿時,才會創建新的線程處理任務。

如果設置的corePoolSize與maximumPoolSize相同,那麼創建的線程池大小是固定的,此時,如果有新任務提交,並且workQueue沒有滿時,就把請求放入到workQueue中,等待空閒的線程,從workQueue中取出任務進行處理。

如果運行的線程數量大於maximumPoolSize,同時,workQueue已經滿了,會通過拒絕策略參數rejectHandler來指定處理策略。

線程池處理任務方式

當提交一個新的任務到線程池時,線程池會根據當前線程池中正在運行的線程數量來決定該任務的處理方式。處理方式總共有三種:直接切換、使用無限隊列、使用有界隊列。

直接切換常用的隊列就是SynchronousQueue。

使用無限隊列就是使用基於鏈表的隊列,比如:LinkedBlockingQueue,如果使用這種方式,線程池中創建的最大線程數就是corePoolSize,此時maximumPoolSize不會起作用。當線程池中所有的核心線程都是運行狀態時,提交新任務,就會放入等待隊列中。

使用有界隊列使用的是ArrayBlockingQueue,使用這種方式可以將線程池的最大線程數量限制為maximumPoolSize,可以降低資源的消耗。但是,這種方式使得線程池對線程的調度更困難,因為線程池和隊列的容量都是有限的了。

降低系統資源消耗措施

如果想降低系統資源的消耗,包括CPU使用率,操作系統資源的消耗,上下文環境切換的開銷等,可以設置一個較大的隊列容量和較小的線程池容量。這樣,會降低線程處理任務的吞吐量。

如果提交的任務經常發生阻塞,可以考慮調用設置最大線程數的方法,重新設置線程池最大線程數。如果隊列的容量設置的較小,通常需要將線程池的容量設置的大一些,這樣,CPU的使用率會高些。如果線程池的容量設置的過大,併發量就會增加,則需要考慮線程調度的問題,反而可能會降低處理任務的吞吐量。

(4)keepAliveTime:線程沒有任務執行時最多保持多久時間終止
當線程池中的線程數量大於corePoolSize時,如果此時沒有新的任務提交,核心線程外的線程不會立即銷燬,需要等待,直到等待的時間超過了keepAliveTime就會終止。
(5)unit:keepAliveTime的時間單位
(6)threadFactory:線程工廠,用來創建線程
默認會提供一個默認的工廠來創建線程,當使用默認的工廠來創建線程時,會使新創建的線程具有相同的優先級,並且是非守護的線程,同時也設置了線程的名稱
(7)rejectHandler:拒絕處理任務時的策略
如果workQueue阻塞隊列滿了,並且沒有空閒的線程池,此時,繼續提交任務,需要採取一種策略來處理這個任務。

線程池總共提供了四種策略:

  • 直接拋出異常,這也是默認的策略。實現類為AbortPolicy。
  • 用調用者所在的線程來執行任務。實現類為CallerRunsPolicy。
  • 丟棄隊列中最靠前的任務並執行當前任務。實現類為DiscardOldestPolicy。
  • 直接丟棄當前任務。實現類為DiscardPolicy。

線程池實例的幾種狀態:

  • Running:運行狀態,能接收新提交的任務,並且也能處理阻塞隊列中的任務
  • Shutdown: 關閉狀態,不能再接收新提交的任務,但是可以處理阻塞隊列中已經保存的任務,當線程池處於Running狀態時,調用shutdown()方法會使線程池進入該狀態
  • Stop: 不能接收新任務,也不能處理阻塞隊列中已經保存的任務,會中斷正在處理任務的線程,如果線程池處於Running或Shutdown狀態,調用shutdownNow()方法,會使線程池進入該狀態
  • Tidying: 如果所有的任務都已經終止,有效線程數為0(阻塞隊列為空,線程池中的工作線程數量為0),線程池就會進入該狀態。
  • Terminated: 處於Tidying狀態的線程池調用terminated()方法,會使用線程池進入該狀態

注意:不需要對線程池的狀態做特殊的處理,線程池的狀態是線程池內部根據方法自行定義和處理的。

ThreadPoolExecutor提供的方法

(1)execute():提交任務,交給線程池執行
(2)submit():提交任務,能夠返回執行結果 execute+Future
(3)shutdown():關閉線程池,等待任務都執行完
(4)shutdownNow():立即關閉線程池,不等待任務執行完

適用於監控的方法如下:

(1)getTaskCount():線程池已執行和未執行的任務總數
(2)getCompletedTaskCount():已完成的任務數量
(3)getPoolSize():線程池當前的線程數量
(4)getActiveCount():當前線程池中正在執行任務的線程數量

線程池 - Executor框架接口

(1)Executors.newCachedThreadPool:創建一個可緩存的線程池,如果線程池的大小超過了需要,可以靈活回收空閒線程,如果沒有可回收線程,則新建線程
(2)Executors.newFixedThreadPool:創建一個定長的線程池,可以控制線程的最大併發數,超出的線程會在隊列中等待
(3)Executors.newScheduledThreadPool:創建一個定長的線程池,支持定時、週期性的任務執行
(4)Executors.newSingleThreadExecutor: 創建一個單線程化的線程池,使用一個唯一的工作線程執行任務,保證所有任務按照指定順序(先入先出或者優先級)執行

線程池 - 合理配置

(1)CPU密集型任務,就需要儘量壓榨CPU,參考值可以設置為NCPU+1(CPU的數量加1)。
(2)IO密集型任務,參考值可以設置為2*NCPU(CPU數量乘以2)

 

點擊關注,第一時間瞭解華為雲新鮮技術~