前言
線程的使用在 Java 開發中已經屢見不鮮了,在并發程式設計,分布式的場景中更是常客,但是對于線程的使用可能一些職場新人還是會有些不熟悉,結合池化技術的線程池也可能尚未有了解,今天這篇我們就先來說一些Java線程池中的一些基本知識以及原理。
線程池ThreadPool
線程池做的工作主要是控制運作的線程的數量,處理過程中将任務放入隊列,然後線上程建立後啟動這些任務,如果線程數量超過了最大數量超出數量的線程排隊等候,等其它線程執行完畢,再從隊列中取出任務來執行。他的主要特點為:線程複用;控制最大并發數;管理線程。
線程池工作流程
- 線程池剛建立時,裡面沒有一個線程。任務隊列是作為參數傳進來的。不過,就算隊列裡面有任務,線程池也不會馬上執行它們。
-
當調用 execute() 方法添加一個任務時,線程池會做如下判斷:
a) 如果正在運作的線程數量小于 corePoolSize,那麼馬上建立線程運作這個任務;
b) 如果正在運作的線程數量大于或等于 corePoolSize,那麼将這個任務放入隊列;
c) 如果這時候隊列滿了,而且正在運作的線程數量小于 maximumPoolSize,那麼還是要建立非核心線程立刻運作這個任務;
d) 如果隊列滿了,而且正在運作的線程數量大于或等于 maximumPoolSize,那麼線程池會抛出異常RejectExecutionException。
- 當一個線程完成任務時,它會從隊列中取下一個任務來執行。
- 當一個線程無事可做,超過一定的時間(keepAliveTime)時,線程池會判斷,如果目前運作的線程數大于 corePoolSize,那麼這個線程就被停掉。是以線程池的所有任務完成後,它最終會收縮到 corePoolSize 的大小。
線程池參數
老規矩,上代碼,下面是ThreadPoolExector的構造函數:
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
- int corePoolSize 核心線程數
- int maximumPoolSize 最大線程數
- long keepAliveTime 目前線程池數量超過corePoolSize時,多餘的空閑線程的存活時間,即多次時間内會被銷毀
- TimeUnit unit 時間機關
- BlockingQueue workQueue 任務隊列,被送出但尚未被執行的任務
- ThreadFactory threadFactory 線程工廠,一般用預設的就好了
- RejectedExecutionHandler handler 拒絕政策,當任務太多來不及處理,如何拒絕任務
拒絕政策
線程池中的線程已經用完了,無法繼續為新任務服務,同時,等待隊列也已經排滿了,再也塞不下新任務了。這時候我們就需要拒絕政策機制合理的處理這個問題。 JDK内置的拒絕政策如下:
- AbortPolicy : 直接抛出異常,阻止系統正常運作。
- CallerRunsPolicy : 隻要線程池未關閉,該政策直接在調用者線程中,運作目前被丢棄的任務。顯然這樣做不會真的丢棄任務,但是,任務送出線程的性能極有可能會急劇下降。
- DiscardOldestPolicy : 丢棄最老的一個請求,也就是即将被執行的一個任務,并嘗試再次送出目前任務。
- DiscardPolicy : 該政策默默地丢棄無法處理的任務,不予任何處理。如果允許任務丢失,這是最好的一種方案。
以上内置拒絕政策均實作了RejectedExecutionHandler接口,若以上政策仍無法滿足實際需要,完全可以自己擴充RejectedExecutionHandler接口
JDK8自帶線程池
newSingleThreadPool
這個線程池隻有一個核心線程在工作,也就是相當于單線程串行執行所有任務。如果這個唯一的線程因為異常結束,那麼會有一個新的線程來替代它。此線程池保證所有任務的執行順序按照任務的送出順序執行。
- corePoolSize : 1,隻有一個核心線程在工作
- maximumPoolSize : 1
- keepAliveTime : 0L
- workQueue : new LinkedBlockingQueue(),無界緩沖隊列
newScheduledThreadPool
newScheduledThreadPool是核心線程池固定,大小無限的線程池,此線程池支援定時以及周期性執行任務的需求,建立一個周期性執行任務的線程池。如果閑置,非核心線程池會在DEFAULT_KEEPALIVEMILLIS時間内回收
- corePoolSize : corePoolSize
- maximumPoolSize : Integer.MAX_VALUE
- keepAliveTime : DEFAULT_KEEPALIVE_MILLIS
- workQueue : new DelayedWorkQueue<>()
newFixedThreadPool
newFixedThreadPool是固定大小的線程池,隻有核心線程,每次送出一個任務就建立一個線程,直到線程達到線程池的最大大小。線程池的大小一旦達到最大值就會保持不變,如果某個線程因為執行異常而結束,那麼線程池會補充一個新線程。多數針對一些很穩定很固定的正規并發線程,多用于伺服器。
- corePoolSize : nThreads
- maximumPoolSize : nThreads
- keepAliveTime : 0L
- workQueue : new LinkedBlockingQueue(),無界緩沖隊列
newCacheThreadPool
這個是無界線程池,如果線程池大小超過了處理任務所需要的線程,那麼就會回收部分空閑(60秒不執行任務)線程,當任務數增加時,此線程池又可以智能地添加新線程來處理任務。
線程池大小完全依賴于作業系統能夠建立的最大線程大小。SynchronousQueue是一個緩沖區為1的阻塞隊列。
緩存型池子通常用于執行一些生存期很短的異步型任務,是以在一些面向連接配接的daemon型Server中用得不多,但對于生存期短的異步任務,它是Executor的首選。
- corePoolSize : 0
- maximumPoolSize : Integer.MAX_VALUE
- keepAliveTime : 60L
- workQueue : new SynchronousQueue(),緩沖區為1的阻塞隊列
By the way
有問題?可以給我留言或私聊
有收獲?那就順手點個贊呗~
當然,也可以到我的公衆号下「6曦軒」,
回複“學習”,即可領取一份
【Java工程師進階架構師的視訊教程】~
回複“面試”,可以獲得:
【本人嘔心瀝血整理的 Java 面試題】
回複“MySQL腦圖”,可以獲得
【MySQL 知識點梳理高清腦圖】
由于我咧,科班出身的程式員,php,Android以及硬體方面都做過,不過最後還是選擇專注于做 Java,是以有啥問題可以到公衆号提問讨論(技術情感傾訴都可以哈哈哈),看到的話會盡快回複,希望可以跟大家共同學習進步,關于服務端架構,Java 核心知識解析,職業生涯,面試總結等文章會不定期堅持推送輸出,歡迎大家關注~~~