線程池介紹:
使用線程池的好處有很多,比如節省系統資源的開銷,節省創建和銷毀線程的時間等,當我們需要處理的任務較多時,就可以使用線程池。
線程池是一種多線程處理形式,處理過程中將任務添加到隊列,然后在創建線程后自動啟動這些任務。線程池線程都是后臺線程。每個線程都使用默認的堆棧大小,以默認的優先級運行,并處于多線程單元中。如果某個線程在托管代碼中空閑(如正在等待某個事件),則線程池將插入另一個輔助線程來使所有處理器保持繁忙。如果所有線程池線程都始終保持繁忙,但隊列中包含掛起的工作,則線程池將在一段時間后創建另一個輔助線程但線程的數目永遠不會超過最大值。超過最大值的線程可以排隊,但他們要等到其他線程完成后才啟動。
1.首先我們先看一下獲取四種線程池的代碼:
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(10); ExecutorService cachedThreadPool = Executors.newCachedThreadPool(); ExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(10); ExecutorService singleThreadPool = Executors.newSingleThreadExecutor();
可以發現這四種線程池都是由Executors類生成的。依次點開四個方法的內部實現發現,它們最終調用的都是同一個ThreadPoolExecutor()的構造器,而區別在于構造器的參數不同。我們來看下ThreadPoolExecutor的參數列表:
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) {
正是由于這幾個參數的不同導致了四種線程池的工作機制不同。參考源碼對于參數的注釋,我們列出參數的含義。
- corePoolSize:核心線程數量,常駐在線程池中的線程,即使它們是空閑的,也不會銷毀,除非設置allowCoreThreadTimeOut的值。
- maximumPoolSize:線程池最大線程數量
- keepAliveTime:超過核心數量的額外線程也就是非核心線程,在空閑指定的最大時間后被銷毀。(假設時間為5s,核心線程數為2,當前線程為4,則超過核心線程數的其余兩個線程在空閑5秒后會被銷毀。)
- unit:時間單位
- workQueue:等待隊列
- threadFactory:生成線程的工廠
- handler:當等待隊列容量滿以及線程池數量達到最大時,如何處理新的任務。
AbortPolicy(默認):直接拋出異常
CallerRunsPolicy:交給調用者所在線程執行。(假設當前調用者線程是Main,那么就交給Main處理)
DiscardOldestPolicy:丟棄最久未處理的任務,再執行當前任務。(最久未處理的,在隊列中其實就是隊列頭節點,查看源碼的確調用是poll()方法)
DiscardPolicy:丟掉該任務,并且不拋異常。
線程池的工作機制:
當持續往線程池添加任務,
當前線程數量小于核心線程數量的時候,新增線程。
當前線程數量達到核心線程數量的時候,將任務放入等待隊列。
當等待隊列滿的時候,繼續創建新線程。
當線程池數量達到最大并且等待隊列也滿的時候,采取拒絕服務策略。
2.接下來我們就根據參數來分析不同的線程池:
FixedThreadPool
public static ExecutorService newFixedThreadPool(int nThreads) { return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()); }
我們可以看到corePoolSize核心線程數量和maximumPoolSize最大線程數量是一致的,并且keepAliveTime為0。workQueue是LinkedBlockingQueue,這是一個鏈表阻塞隊列??梢缘贸鼋Y論:該線程池是一個固定數量的線程池,并且有一個無界的等待隊列。我們可以推導出該線程池適合處理任務量平穩的場景。例如平均一秒接收10個任務,接收任務量曲線不會很陡峭。
適合場景:適合少量的大任務(大任務處理慢,如果線程數量多的話,反而在切換線程上下文時損耗,所以控制線程在一定的數量)。
CachedThreadPool
public static ExecutorService newCachedThreadPool() { return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>()); }
我們可以看到corePoolSize核心線程池為0,代表該線程沒有核心線程池,意味著線程都是可被回收銷毀的,線程池中有時會是空的。并且maximumPoolSize是int最大值,相當于代表該線程池可以無限創建線程。keepAliveTime為60,代表空閑60秒回收線程。workQueue是SynchronousQueue,該同步隊列是一個沒有容量隊列,即一個任務到來后,要等待線程來消費,才能再繼續添加任務。我們推導出該線程池適合處理平時沒什么任務量,但有時任務量瞬間劇增的場景。
適合場景:大量的小任務(每個任務處理快,不會頻繁出現線程處理一半時,切換其他線程)。
ScheduledThreadPool
public ScheduledThreadPoolExecutor(int corePoolSize) { super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS, new DelayedWorkQueue()); }
我們可以看到該線程池的corePoolSize核心線程數量和maximumPoolSize最大線程數量都是1,代表該線程有且只有一個固定的線程,既然是單線程,所以該線程池實現的是串行操作,沒有并發效果。workQueue是LinkedBlockingQueue,這是一個鏈表阻塞隊列。所以該線程池適合執行串行執行隊列中的任務。
適合場景:按順序串行處理的任務。
可能讀者會好奇keepAliveTime為0代表的含義? 是立即回收線程還是永不回收呢?
keepAliveTime參數注釋明確指明只對非核心線程有用。 我們可以從ScheduledThreadPool的源碼中推測,如果0代表是永不回收的話,那么ScheduledThreadPool一旦創建出非核心線程的話就不會回收了?這樣是很不合理的。所以筆者認為0代表立即回收。
public ScheduledThreadPoolExecutor(int corePoolSize) { super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS, new DelayedWorkQueue()); }
此文章如有錯誤懇請評論指正。希望能通過這篇文章讓大家對線程池有更深的理解。
最后,需要關于分布式,微服務,性能優化,Spring,MyBatis,MySQL數據庫等等的源碼知識點請關注我哦!還有各種常見面試題詳解。(關注+轉發,私信我“學習”,就可以領取架構資料了哦!)
我是小架,祝大家工作上能夠事業有成,學習上也可以打破瓶頸一路向上!