本專欄專一分享大型Bat面試知識,後續會持續更新,喜歡的話麻煩點擊一個關注
線程池能夠簡單看作是一組線程的集合,經過使用線程池,咱們能夠方便的複用線程,避免了頻繁建立和銷燬線程所帶來的開銷。在應用上,線程池可應用在後端相關服務中。好比 Web 服務器,數據庫服務器等。以 Web 服務器爲例,假如 Web 服務器會收到大量短時的 HTTP 請求,若是此時咱們簡單的爲每一個 HTTP 請求建立一個處理線程,那麼服務器的資源將會很快被耗盡。固然咱們也能夠本身去管理並複用已建立的線程,以限制資源的消耗量,但這樣會使用程序的邏輯變複雜。好在,幸運的是,咱們沒必要那樣作。在 JDK 1.5 中,官方已經提供了強大的線程池工具類。經過使用這些工具類,咱們能夠用低廉的代價使用多線程技術。java
線程池做爲 Java 併發重要的工具類,在會用的基礎上,我以爲頗有必要去學習一下線程池的相關原理。畢竟線程池除了要管理線程,還要管理任務,同時還要具有統計功能。因此多瞭解一點,仍是能夠擴充眼界的,同時也能夠更爲熟悉線程池技術。面試
線程池所涉及到的接口和類並非不少,其繼承體系也相對簡單。相關繼承關係以下:數據庫
如上圖,最頂層的接口 Executor 僅聲明瞭一個方法execute
。ExecutorService 接口在其父類接口基礎上,聲明瞭包含但不限於shutdown
、submit
、invokeAll
、invokeAny
等方法。至於 ScheduledExecutorService 接口,則是聲明瞭一些和定時任務相關的方法,好比 schedule
和scheduleAtFixedRate
。線程池的核心實現是在 ThreadPoolExecutor 類中,咱們使用 Executors 調用newFixedThreadPool
、newSingleThreadExecutor
和newCachedThreadPool
等方法建立線程池均是 ThreadPoolExecutor 類型。後端
以上是對線程池繼承體系的簡單介紹,這裏先讓你們對線程池大體輪廓有必定的瞭解。接下來我會介紹一下線程池的實現原理,繼續往下看吧。數組
如上節所說,線程池的核心實現即 ThreadPoolExecutor 類。該類包含了幾個核心屬性,這些屬性在可在構造方法進行初始化。在介紹核心屬性前,咱們先來看看 ThreadPoolExecutor 的構造方法,以下:緩存
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler)
如上所示,構造方法的參數即核心參數,這裏我用一個表格來簡要說明一下各個參數的意義。以下:服務器
參數 | 說明 |
---|---|
corePoolSize | 核心線程數。當線程數小於該值時,線程池會優先建立新線程來執行新任務 |
maximumPoolSize | 線程池所能維護的最大線程數 |
keepAliveTime | 空閒線程的存活時間 |
workQueue | 任務隊列,用於緩存未執行的任務 |
threadFactory | 線程工廠。可經過工廠爲新建的線程設置更有意義的名字 |
handler | 拒絕策略。當線程池和任務隊列均處於飽和狀態時,使用拒絕策略處理新任務。默認是 AbortPolicy,即直接拋出異常 |
以上是各個參數的簡介,下面我將會針對部分參數進行詳細說明,繼續往下看。多線程
在 Java 線程池實現中,線程池所能建立的線程數量受限於 corePoolSize 和 maximumPoolSize 兩個參數值。線程的建立時機則和 corePoolSize 以及 workQueue 兩個參數有關。下面列舉一下線程建立的4個規則(線程池中無空閒線程),以下:併發
簡化一下上面的規則:工具
序號 | 條件 | 動做 |
---|---|---|
1 | 線程數 < corePoolSize | 建立新線程 |
2 | 線程數 ≥ corePoolSize,且 workQueue 未滿 | 緩存新任務 |
3 | corePoolSize ≤ 線程數 < maximumPoolSize,且 workQueue 已滿 | 建立新線程 |
4 | 線程數 ≥ maximumPoolSize,且 workQueue 已滿 | 使用拒絕策略處理 |
考慮到系統資源是有限的,對於線程池超出 corePoolSize 數量的空閒線程應進行回收操做。進行此操做存在一個問題,即回收時機。目前的實現方式是當線程空閒時間超過 keepAliveTime 後,進行回收。除了核心線程數以外的線程能夠進行回收,核心線程內的空閒線程也能夠進行回收。回收的前提是allowCoreThreadTimeOut
屬性被設置爲 true,經過public void allowCoreThreadTimeOut(boolean)
方法能夠設置屬性值。
如3.1.2 線程建立規則一節中規則2所說,當線程數量大於等於 corePoolSize,workQueue 未滿時,則緩存新任務。這裏要考慮使用什麼類型的容器緩存新任務,經過 JDK 文檔介紹,咱們可知道有3中類型的容器可供使用,分別是同步隊列
,有界隊列
和無界隊列
。對於有優先級的任務,這裏還能夠增長優先級隊列
。以上所介紹的4中類型的隊列,對應的實現類以下:
實現類 | 類型 | 說明 |
---|---|---|
SynchronousQueue | 同步隊列 | 該隊列不存儲元素,每一個插入操做必須等待另外一個線程調用移除操做,不然插入操做會一直阻塞 |
ArrayBlockingQueue | 有界隊列 | 基於數組的阻塞隊列,按照 FIFO 原則對元素進行排序 |
LinkedBlockingQueue | 無界隊列 | 基於鏈表的阻塞隊列,按照 FIFO 原則對元素進行排序 |
PriorityBlockingQueue | 優先級隊列 | 具備優先級的阻塞隊列 |
如3.1.2 線程建立規則一節中規則4所說,線程數量大於等於 maximumPoolSize,且 workQueue 已滿,則使用拒絕策略處理新任務。Java 線程池提供了4中拒絕策略實現類,以下:
實現類 | 說明 |
---|---|
AbortPolicy | 丟棄新任務,並拋出 RejectedExecutionException |
DiscardPolicy | 不作任何操做,直接丟棄新任務 |
DiscardOldestPolicy | 丟棄隊列隊首的元素,並執行新任務 |
CallerRunsPolicy | 由調用線程執行新任務 |
以上4個拒絕策略中,AbortPolicy 是線程池實現類所使用的策略。咱們也能夠經過方法public void setRejectedExecutionHandler(RejectedExecutionHandler)
修改線程池決絕策略。
在線程池的實現上,線程的建立是經過線程工廠接口ThreadFactory
的實現類來完成的。默認狀況下,線程池使用Executors.defaultThreadFactory()
方法返回的線程工廠實現類。固然,咱們也能夠經過
public void setThreadFactory(ThreadFactory)
方法進行動態修改。具體細節這裏就很少說了,並不複雜,你們能夠本身去看下源碼。
在線程池中,線程的複用是線程池的關鍵所在。這就要求線程在執行完一個任務後,不能當即退出。對應到具體實現上,工做線程在執行完一個任務後,會再次到任務隊列獲取新的任務。若是任務隊列中沒有任務,且 keepAliveTime 也未被設置,工做線程則會被一致阻塞下去。經過這種方式便可實現線程複用。
說完原理,再來看看線程的建立和複用的相關代碼(基於 JDK 1.8),以下:
+----ThreadPoolExecutor.Worker.java Worker(Runnable firstTask) { setState(-1); this.firstTask = firstTask; // 調用線程工廠建立線程 this.thread = getThreadFactory().newThread(this); } // Worker 實現了 Runnable 接口 public void run() { runWorker(this); } +----ThreadPoolExecutor.java final void runWorker(Worker w) { Thread wt = Thread.currentThread(); Runnable task = w.firstTask; w.firstTask = null; w.unlock(); boolean completedAbruptly = true; try { // 循環從任務隊列中獲取新任務 while (task != null || (task = getTask()) != null) { w.lock(); // If pool is stopping, ensure thread is interrupted; // if not, ensure thread is not interrupted. This // requires a recheck in second case to deal with // shutdownNow race while clearing interrupt if ((runStateAtLeast(ctl.get(), STOP) || (Thread.interrupted() && runStateAtLeast(ctl.get(), STOP))) && !wt.isInterrupted()) wt.interrupt(); try { beforeExecute(wt, task); Throwable thrown = null; try { // 執行新任務 task.run(); } catch (RuntimeException x) { thrown = x; throw x; } catch (Error x) { thrown = x; throw x; } catch (Throwable x) { thrown = x; throw new Error(x); } finally { afterExecute(task, thrown); } } finally { task = null; w.completedTasks++; w.unlock(); } } completedAbruptly = false; } finally { // 線程退出後,進行後續處理 processWorkerExit(w, completedAbruptly); } }
一般狀況下,咱們能夠經過線程池的submit
方法提交任務。被提交的任務可能會當即執行,也可能會被緩存或者被拒絕。任務的處理流程以下圖所示:
上面的流程圖不是很複雜,下面再來看看流程圖對應的代碼,以下:
+---- AbstractExecutorService.java public Future<?> submit(Runnable task) { if (task == null) throw new NullPointerException(); // 建立任務 RunnableFuture<Void> ftask = newTaskFor(task, null); // 提交任務 execute(ftask); return ftask; } +---- ThreadPoolExecutor.java public void execute(Runnable command) { if (command == null) throw new NullPointerException(); int c = ctl.get(); // 若是工做線程數量 < 核心線程數,則建立新線程 if (workerCountOf(c) < corePoolSize) { // 添加工做者對象 if (addWorker(command, true)) return; c = ctl.get(); } // 緩存任務,若是隊列已滿,則 offer 方法返回 false。不然,offer 返回 true if (isRunning(c) && workQueue.offer(command)) { int recheck = ctl.get(); if (! isRunning(recheck) && remove(command)) reject(command); else if (workerCountOf(recheck) == 0) addWorker(null, false); } // 添加工做者對象,並在 addWorker 方法中檢測線程數是否小於最大線程數 else if (!addWorker(command, false)) // 線程數 >= 最大線程數,使用拒絕策略處理任務 reject(command); } private boolean addWorker(Runnable firstTask, boolean core) { retry: for (;;) { int c = ctl.get(); int rs = runStateOf(c); // Check if queue empty only if necessary. if (rs >= SHUTDOWN && ! (rs == SHUTDOWN && firstTask == null && ! workQueue.isEmpty())) return false; for (;;) { int wc = workerCountOf(c); // 檢測工做線程數與核心線程數或最大線程數的關係 if (wc >= CAPACITY || wc >= (core ? corePoolSize : maximumPoolSize)) return false; if (compareAndIncrementWorkerCount(c)) break retry; c = ctl.get(); // Re-read ctl if (runStateOf(c) != rs) continue retry; // else CAS failed due to workerCount change; retry inner loop } } boolean workerStarted = false; boolean workerAdded = false; Worker w = null; try { // 建立工做者對象,細節參考上一節所貼代碼 w = new Worker(firstTask); final Thread t = w.thread; if (t != null) { final ReentrantLock mainLock = this.mainLock; mainLock.lock(); try { int rs = runStateOf(ctl.get()); if (rs < SHUTDOWN || (rs == SHUTDOWN && firstTask == null)) { if (t.isAlive()) // precheck that t is startable throw new IllegalThreadStateException(); // 將 worker 對象添加到 workers 集合中 workers.add(w); int s = workers.size(); // 更新 largestPoolSize 屬性 if (s > largestPoolSize) largestPoolSize = s; workerAdded = true; } } finally { mainLock.unlock(); } if (workerAdded) { // 開始執行任務 t.start(); workerStarted = true; } } } finally { if (! workerStarted) addWorkerFailed(w); } return workerStarted; }
上面的代碼略多,不過結合上面的流程圖,和我所寫的註釋,理解主邏輯應該不難。
咱們能夠經過shutdown
和shutdownNow
兩個方法關閉線程池。兩個方法的區別在於,shutdown 會將線程池的狀態設置爲SHUTDOWN
,同時該方法還會中斷空閒線程。shutdownNow 則會將線程池狀態設置爲STOP
,並嘗試中斷全部的線程。中斷線程使用的是Thread.interrupt
方法,未響應中斷方法的任務是沒法被中斷的。最後,shutdownNow 方法會將未執行的任務所有返回。
調用 shutdown 和 shutdownNow 方法關閉線程池後,就不能再向線程池提交新任務了。對於處於關閉狀態的線程池,會使用拒絕策略處理新提交的任務。
通常狀況下,咱們並不直接使用 ThreadPoolExecutor 類建立線程池,而是經過 Executors 工具類去構建線程池。經過 Executors 工具類,咱們能夠構造5中不一樣的線程池。下面經過一個表格簡單介紹一下幾種線程池,以下:
靜態構造方法 | 說明 |
---|---|
newFixedThreadPool(int nThreads) | 構建包含固定線程數的線程池,默認狀況下,空閒線程不會被回收 |
newCachedThreadPool() | 構建線程數不定的線程池,線程數量隨任務量變更,空閒線程存活時間超過60秒後會被回收 |
newSingleThreadExecutor() | 構建線程數爲1的線程池,等價於 newFixedThreadPool(1) 所構造出的線程池 |
newScheduledThreadPool(int corePoolSize) | 構建核心線程數爲 corePoolSize,可執行定時任務的線程池 |
newSingleThreadScheduledExecutor() | 等價於 newScheduledThreadPool(1) |
更多信息能夠點擊關於我 , 很是但願和你們一塊兒交流 , 共同進步
領取以下資料:
https://shimo.im/docs/Jp8PTkp... 《Android面試.進階.視頻大全目錄》