深刻分析java線程池的實現原理

前言
線程是稀缺資源,若是被無限制的建立,不只會消耗系統資源,還會下降系統的穩定性,合理的使用線程池對線程進行統一分配、調優和監控,有如下好處:
一、下降資源消耗;
二、提升響應速度;
三、提升線程的可管理性。java

Java1.5中引入的Executor框架把任務的提交和執行進行解耦,只須要定義好任務,而後提交給線程池,而不用關心該任務是如何執行、被哪一個線程執行,以及何時執行。數組

demo
深刻分析java線程池的實現原理緩存

一、Executors.newFixedThreadPool(10)初始化一個包含10個線程的線程池executor;
二、經過executor.execute方法提交20個任務,每一個任務打印當前的線程名;
三、負責執行任務的線程的生命週期都由Executor框架進行管理;架構

ThreadPoolExecutor
Executors是java線程池的工廠類,經過它能夠快速初始化一個符合業務需求的線程池,如Executors.newFixedThreadPool方法能夠生成一個擁有固定線程數的線程池。
深刻分析java線程池的實現原理併發

其本質是經過不一樣的參數初始化一個ThreadPoolExecutor對象,具體參數描述以下:框架

corePoolSize
線程池中的核心線程數,當提交一個任務時,線程池建立一個新線程執行任務,直到當前線程數等於corePoolSize;若是當前線程數爲corePoolSize,繼續提交的任務被保存到阻塞隊列中,等待被執行;若是執行了線程池的prestartAllCoreThreads()方法,線程池會提早建立並啓動全部核心線程。異步

maximumPoolSize
線程池中容許的最大線程數。若是當前阻塞隊列滿了,且繼續提交任務,則建立新的線程執行任務,前提是當前線程數小於maximumPoolSize;ide

keepAliveTime
線程空閒時的存活時間,即當線程沒有任務執行時,繼續存活的時間;默認狀況下,該參數只在線程數大於corePoolSize時纔有用;性能

unit
keepAliveTime的單位;學習

workQueue
用來保存等待被執行的任務的阻塞隊列,且任務必須實現Runable接口,在JDK中提供了以下阻塞隊列:
一、ArrayBlockingQueue:基於數組結構的有界阻塞隊列,按FIFO排序任務;
二、LinkedBlockingQuene:基於鏈表結構的阻塞隊列,按FIFO排序任務,吞吐量一般要高於ArrayBlockingQuene;
三、SynchronousQuene:一個不存儲元素的阻塞隊列,每一個插入操做必須等到另外一個線程調用移除操做,不然插入操做一直處於阻塞狀態,吞吐量一般要高於LinkedBlockingQuene;
四、priorityBlockingQuene:具備優先級的×××阻塞隊列;

threadFactory
建立線程的工廠,經過自定義的線程工廠能夠給每一個新建的線程設置一個具備識別度的線程名。
深刻分析java線程池的實現原理

handler
線程池的飽和策略,當阻塞隊列滿了,且沒有空閒的工做線程,若是繼續提交任務,必須採起一種策略處理該任務,線程池提供了4種策略:
一、AbortPolicy:直接拋出異常,默認策略;
二、CallerRunsPolicy:用調用者所在的線程來執行任務;
三、DiscardOldestPolicy:丟棄阻塞隊列中靠最前的任務,並執行當前任務;
四、DiscardPolicy:直接丟棄任務;
固然也能夠根據應用場景實現RejectedExecutionHandler接口,自定義飽和策略,如記錄日誌或持久化存儲不能處理的任務。

Exectors
Exectors工廠類提供了線程池的初始化接口,主要有以下幾種:

newFixedThreadPool
深刻分析java線程池的實現原理

初始化一個指定線程數的線程池,其中corePoolSize == maximumPoolSize,使用LinkedBlockingQuene做爲阻塞隊列,不過當線程池沒有可執行任務時,也不會釋放線程。

newCachedThreadPool
深刻分析java線程池的實現原理

一、初始化一個能夠緩存線程的線程池,默認緩存60s,線程池的線程數可達到Integer.MAX_VALUE,即2147483647,內部使用SynchronousQueue做爲阻塞隊列;
二、和newFixedThreadPool建立的線程池不一樣,newCachedThreadPool在沒有任務執行時,當線程的空閒時間超過keepAliveTime,會自動釋放線程資源,當提交新任務時,若是沒有空閒線程,則建立新線程執行任務,會致使必定的系統開銷;

因此,使用該線程池時,必定要注意控制併發的任務數,不然建立大量的線程可能致使嚴重的性能問題。

newSingleThreadExecutor
深刻分析java線程池的實現原理

初始化的線程池中只有一個線程,若是該線程異常結束,會從新建立一個新的線程繼續執行任務,惟一的線程能夠保證所提交任務的順序執行,內部使用LinkedBlockingQueue做爲阻塞隊列。

newScheduledThreadPool
深刻分析java線程池的實現原理

初始化的線程池能夠在指定的時間內週期性的執行所提交的任務,在實際的業務場景中可使用該線程池按期的同步數據。

實現原理
除了newScheduledThreadPool的內部實現特殊一點以外,其它幾個線程池都是基於ThreadPoolExecutor類實現的。

線程池內部狀態
深刻分析java線程池的實現原理

其中AtomicInteger變量ctl的功能很是強大:利用低29位表示線程池中線程數,經過高3位表示線程池的運行狀態:
一、RUNNING:-1 << COUNT_BITS,即高3位爲111,該狀態的線程池會接收新任務,並處理阻塞隊列中的任務;
二、SHUTDOWN: 0 << COUNT_BITS,即高3位爲000,該狀態的線程池不會接收新任務,但會處理阻塞隊列中的任務;
三、STOP : 1 << COUNT_BITS,即高3位爲001,該狀態的線程不會接收新任務,也不會處理阻塞隊列中的任務,並且會中斷正在運行的任務;
四、TIDYING : 2 << COUNT_BITS,即高3位爲010;
五、TERMINATED: 3 << COUNT_BITS,即高3位爲011;

任務提交
線程池框架提供了兩種方式提交任務,根據不一樣的業務需求選擇不一樣的方式。

Executor.execute()
深刻分析java線程池的實現原理

經過Executor.execute()方法提交的任務,必須實現Runnable接口,該方式提交的任務不能獲取返回值,所以沒法判斷任務是否執行成功。

ExecutorService.submit()
深刻分析java線程池的實現原理

經過ExecutorService.submit()方法提交的任務,能夠獲取任務執行完的返回值。

任務執行
當向線程池中提交一個任務,線程池會如何處理該任務?

execute實現
深刻分析java線程池的實現原理

具體的執行流程以下:
一、workerCountOf方法根據ctl的低29位,獲得線程池的當前線程數,若是線程數小於corePoolSize,則執行addWorker方法建立新的線程執行任務;不然執行步驟(2);
二、若是線程池處於RUNNING狀態,且把提交的任務成功放入阻塞隊列中,則執行步驟(3),不然執行步驟(4);
三、再次檢查線程池的狀態,若是線程池沒有RUNNING,且成功從阻塞隊列中刪除任務,則執行reject方法處理任務;
四、執行addWorker方法建立新的線程執行任務,若是addWoker執行失敗,則執行reject方法處理任務;
深刻分析java線程池的實現原理

addWorker實現
從方法execute的實現能夠看出:addWorker主要負責建立新的線程並執行任務,代碼實現以下:
深刻分析java線程池的實現原理

這只是addWoker方法實現的前半部分:
一、判斷線程池的狀態,若是線程池的狀態值大於或等SHUTDOWN,則不處理提交的任務,直接返回;
二、經過參數core判斷當前須要建立的線程是否爲核心線程,若是core爲true,且當前線程數小於corePoolSize,則跳出循環,開始建立新的線程,具體實現以下:

深刻分析java線程池的實現原理
線程池的工做線程經過Woker類實現,在ReentrantLock鎖的保證下,把Woker實例插入到HashSet後,並啓動Woker中的線程,其中Worker類設計以下:
一、繼承了AQS類,能夠方便的實現工做線程的停止操做;
二、實現了Runnable接口,能夠將自身做爲一個任務在工做線程中執行;
三、當前提交的任務firstTask做爲參數傳入Worker的構造方法;

深刻分析java線程池的實現原理

從Woker類的構造方法實現能夠發現:線程工廠在建立線程thread時,將Woker實例自己this做爲參數傳入,當執行start方法啓動線程thread時,本質是執行了Worker的runWorker方法。

runWorker實現
深刻分析java線程池的實現原理

runWorker方法是線程池的核心:
一、線程啓動以後,經過unlock方法釋放鎖,設置AQS的state爲0,表示運行中斷;
二、獲取第一個任務firstTask,執行任務的run方法,不過在執行任務以前,會進行加鎖操做,任務執行完會釋放鎖;
三、在執行任務的先後,能夠根據業務場景自定義beforeExecute和afterExecute方法;
四、firstTask執行完成以後,經過getTask方法從阻塞隊列中獲取等待的任務,若是隊列中沒有任務,getTask方法會被阻塞並掛起,不會佔用cpu資源;

getTask實現
深刻分析java線程池的實現原理

整個getTask操做在自旋下完成:
一、workQueue.take:若是阻塞隊列爲空,當前線程會被掛起等待;當隊列中有任務加入時,線程被喚醒,take方法返回任務,並執行;
二、workQueue.poll:若是在keepAliveTime時間內,阻塞隊列仍是沒有任務,則返回null;

因此,線程池中實現的線程能夠一直執行由用戶提交的任務。

Future和Callable實現
深刻分析java線程池的實現原理
經過ExecutorService.submit()方法提交的任務,能夠獲取任務執行完的返回值。

在實際業務場景中,Future和Callable基本是成對出現的,Callable負責產生結果,Future負責獲取結果。
一、Callable接口相似於Runnable,只是Runnable沒有返回值。
二、Callable任務除了返回正常結果以外,若是發生異常,該異常也會被返回,即Future能夠拿到異步執行任務各類結果;
三、Future.get方法會致使主線程阻塞,直到Callable任務執行完成;

submit實現
深刻分析java線程池的實現原理

經過submit方法提交的Callable任務會被封裝成了一個FutureTask對象。

FutureTask
深刻分析java線程池的實現原理

一、FutureTask在不一樣階段擁有不一樣的狀態state,初始化爲NEW;
二、FutureTask類實現了Runnable接口,這樣就能夠經過Executor.execute方法提交FutureTask到線程池中等待被執行,最終執行的是FutureTask的run方法;

FutureTask.get實現
深刻分析java線程池的實現原理

內部經過awaitDone方法對主線程進行阻塞,具體實現以下:
深刻分析java線程池的實現原理

一、若是主線程被中斷,則拋出中斷異常;
二、判斷FutureTask當前的state,若是大於COMPLETING,說明任務已經執行完成,則直接返回;
三、若是當前state等於COMPLETING,說明任務已經執行完,這時主線程只需經過yield方法讓出cpu資源,等待state變成NORMAL;
四、經過WaitNode類封裝當前線程,並經過UNSAFE添加到waiters鏈表;
五、最終經過LockSupport的park或parkNanos掛起線程;

FutureTask.run實現
深刻分析java線程池的實現原理
FutureTask.run方法是在線程池中被執行的,而非主線程
一、經過執行Callable任務的call方法;
二、若是call執行成功,則經過set方法保存結果;
三、若是call執行有異常,則經過setException保存異常;

set
深刻分析java線程池的實現原理

setException
深刻分析java線程池的實現原理

set和setException方法中,都會經過UnSAFE修改FutureTask的狀態,並執行finishCompletion方法通知主線程任務已經執行完成;

finishCompletion
深刻分析java線程池的實現原理

一、執行FutureTask類的get方法時,會把主線程封裝成WaitNode節點並保存在waiters鏈表中;
二、FutureTask任務執行完成後,經過UNSAFE設置waiters的值,並經過LockSupport類unpark方法喚醒主線程;

歡迎工做一到五年的Java工程師朋友們加入Java架構開發:855801563 獲取更多免費視頻教程。

合理利用本身每一分每一秒的時間來學習提高本身,不要再用"沒有時間「來掩飾本身思想上的懶惰!趁年輕,使勁拼,給將來的本身一個交代

相關文章
相關標籤/搜索