(1):線程池存在哪些狀態,這些狀態之間是如何進行切換的呢?數組
(2):線程池的種類有哪些?緩存
(3):建立線程池須要哪些參數,這些參數的具體含義是什麼?併發
(4):將任務添加到線程池以後運行流程?函數
(5):線程池是怎麼作到重用線程的呢?this
(6):線程池的關閉spa
首先回答第一個問題:線程池存在哪些狀態;線程
查看ThreadPoolExecutor源碼便知曉:code
// runState is stored in the high-order bits private static final int RUNNING = -1 << COUNT_BITS; private static final int SHUTDOWN = 0 << COUNT_BITS; private static final int STOP = 1 << COUNT_BITS; private static final int TIDYING = 2 << COUNT_BITS; private static final int TERMINATED = 3 << COUNT_BITS;
存在5種狀態:對象
<1>Running:能夠接受新任務,同時也能夠處理阻塞隊列裏面的任務;blog
<2>Shutdown:不能夠接受新任務,可是能夠處理阻塞隊列裏面的任務;
<3>Stop:不能夠接受新任務,也不處理阻塞隊列裏面的任務,同時還中斷正在處理的任務;
<4>Tidying:屬於過渡階段,在這個階段表示全部的任務已經執行結束了,當前線程池中是不存在有效的線程的,而且將要調用terminated方法;
<5>Terminated:終止狀態,這個狀態是在調用完terminated方法以後所處的狀態;
那麼這5種狀態之間是如何進行轉換的呢?查看ThreadPoolExecutor源碼裏面的註釋即可以知道啦:
* RUNNING -> SHUTDOWN * On invocation of shutdown(), perhaps implicitly in finalize() * (RUNNING or SHUTDOWN) -> STOP * On invocation of shutdownNow() * SHUTDOWN -> TIDYING * When both queue and pool are empty * STOP -> TIDYING * When pool is empty * TIDYING -> TERMINATED * When the terminated() hook method has completed
從上面能夠看到,在調用shutdown方法的時候,線程池狀態會從Running轉換成Shutdown;在調用shutdownNow方法的時候,線程池狀態會從Running/Shutdown轉換成Stop;在阻塞隊列爲空同時線程池爲空的狀況下,線程池狀態會從Shutdown轉換成Tidying;在線程池爲空的狀況下,線程池狀態會從Stop轉換成Tidying;當調用terminated方法以後,線程池狀態會從Tidying轉換成Terminate;
在明白了線程池的各個狀態以及狀態之間是怎麼進行切換以後,咱們來看看第二個問題,線程池的種類:
(1):CachedThreadPool:緩存線程池,該類線程池中線程的數量是不肯定的,理論上能夠達到Integer.MAX_VALUE個,這種線程池中的線程都是非核心線程,既然是非核心線程,那麼就存在超時淘汰機制了,當裏面的某個線程空閒時間超過了設定的超時時間的話,就會回收掉該線程;
allowCoreThreadTimeOut
(3):ScheduledThreadPool:任務線程池,這種線程池中核心線程的數量是固定的,而對於非核心線程的數量是不限制的,同時對於非核心線程是存在超時淘汰機制的,主要適用於執行定時任務或者週期性任務的場景;
(4):SingleThreadPool:單一線程池,線程池裏面只有一個線程,同時也不存在非核心線程,感受像是FixedThreadPool的特殊版本,他主要用於確保任務在同一線程中的順序執行,有點相似於進行同步吧;
接下來咱們來看第三個問題,建立線程池須要哪些參數:
一樣查看ThreadPoolExecutor源碼,查看建立線程池的構造函數:
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler)
無論你調用的是ThreadPoolExecutor的哪一個構造函數,最終都會執行到這個構造函數的,這個構造函數有7個參數,正是因爲對這7個參數值的賦值不一樣,形成生成不一樣類型的線程池,好比咱們常見的CachedThreadPoolExecutor、FixedThreadPoolExecutor
SingleThreadPoolExecutor、ScheduledThreadPoolExecutor,咱們老看看這幾個參數的具體含義:
<1>corePoolSize:線程池中核心線程的數量;當提交一個任務到線程池的時候,線程池會建立一個線程來執行執行任務,即便有其餘空閒的線程存在,直到線程數達到corePoolSize時再也不建立,這時候會把提交的新任務放入到阻塞隊列中,若是調用了線程池的preStartAllCoreThreads方法,則會在建立線程池的時候初始化出來核心線程;
<2>maximumPoolSize:線程池容許建立的最大線程數;若是阻塞隊列已經滿了,同時已經建立的線程數小於最大線程數的話,那麼會建立新的線程來處理阻塞隊列中的任務;
<3>keepAliveTime:線程活動保持時間,指的是工做線程空閒以後繼續存活的時間,默認狀況下,這個參數只有線程數大於corePoolSize的時候纔會起做用,即當線程池中的線程數目大於corePoolSize的時候,若是某一個線程的空閒時間達到keepAliveTime,那麼這個線程是會被終止的,直到線程池中的線程數目不大於corePoolSize;若是調用allowCoreThreadTimeOut的話,在線程池中線程數量不大於corePoolSize的時候,keepAliveTime參數也能夠起做用的,知道線程數目爲0爲止;
<4>unit:參數keepAliveTime的時間單位;
<5>workQueue:阻塞隊列;用於存儲等待執行的任務,有四種阻塞隊列類型,ArrayBlockingQueue(基於數組的有界阻塞隊列)、LinkedBlockingQueue(基於鏈表結構的阻塞隊列)、SynchronousQueue(不存儲元素的阻塞隊列)、PriorityBlockingQueue(具備優先級的阻塞隊列);
<6>threadFactory:用於建立線程的線程工廠;
<7>handler:當阻塞隊列滿了,且沒有空閒線程的狀況下,也就是說這個時候,線程池中的線程數目已經達到了最大線程數量,處於飽和狀態,那麼必須採起一種策略來處理新提交的任務,咱們能夠本身定義處理策略,也可使用系統已經提供給咱們的策略,先來看看系統爲咱們提供的4種策略,AbortPolicy(直接拋出異常)、CallerRunsPolicy(只有調用者所在的線程來運行任務)、DiscardOldestPolicy(丟棄阻塞隊列中最近的一個任務,並執行當前任務)、Discard(直接丟棄);
接下來就是將任務添加到線程池以後的運行流程了;
咱們能夠調用submit或者execute方法,二者最大的區別在於,調用submit方法的話,咱們能夠傳入一個實現Callable接口的對象,進而能在當前任務執行結束以後經過Future對象得到任務的返回值,submit內部實際上仍是執行的execute方法;而調用execute方法的話,是不能得到任務執行結束以後的返回值的;此外,調用submit方法的話是能夠拋出異常的,可是調用execute方法的話,異常在其內部獲得了消化,也就是說異常在其內部獲得了處理,不會向外傳遞的;
由於submit方法最終也是會執行execute方法的,所以咱們只須要了解execute方法就能夠了:
在execute方法內部會分三種狀況來進行處理:
<1>:首先判斷當前線程池中的線程數量是否小於corePoolSize,若是小於的話,則直接經過addWorker方法建立一個新的Worker對象來執行咱們當前的任務;
<2>:若是說當前線程池中的線程數量大於corePoolSize的話,那麼會嘗試將當前任務添加到阻塞隊列中,而後第二次檢查線程池的狀態,若是線程池不在Running狀態的話,會將剛剛添加到阻塞隊列中的任務移出,同時拒絕當前任務請求;若是第二次檢查發現當前線程池處於Running狀態的話,那麼會查看當前線程池中的工做線程數量是否爲0,若是爲0的話,就會經過addWorker方法建立一個Worker對象出來處理阻塞隊列中的任務;
<3>:若是原先線程池就不處於Running狀態或者咱們剛剛將當前任務添加到阻塞隊列的時候出現錯誤的話,那麼會去嘗試經過addWorker建立新的Worker來處理當前任務,若是添加失敗的話,則拒絕當前任務請求;
能夠看到在上面的execute方法中,咱們僅僅只是檢查了當前線程池中的線程數量有沒有超過corePoolSize的狀況,那麼當前線程池中的線程數量有沒有超過maximumPoolSize是在哪裏檢測的呢?其實是在addWorker方法裏面了,咱們能夠看下addWorker裏面的一段代碼:
if (wc >= CAPACITY || wc >= (core ? corePoolSize : maximumPoolSize)) return false;
若是當前線程數量超過maximumPoolSize的話,直接就會調用return方法,返回false;
其實到這裏咱們很明顯能夠知道,一個線程池中線程的數量實際上就是這個線程池中Worker的數量,若是Worker的大小超過了corePoolSize,那麼任務都在阻塞隊列裏面了,Worker是Java對咱們任務的一個封裝類,他的聲明是醬紫的:
private final class Worker extends AbstractQueuedSynchronizer implements Runnable
能夠看到他實現了Runnable接口,他是在addWorker方法裏面經過new Worker(firstTask)建立的,咱們來看看他的構造函數就知道了:
Worker(Runnable firstTask) { setState(-1); // inhibit interrupts until runWorker this.firstTask = firstTask; this.thread = getThreadFactory().newThread(this); }
而這裏的firstTask其實就是咱們調用execute或者submit的時候傳入的那個參數罷了,通常來講這些參數是實現Callable或者Runnable接口的;
在經過addWorker方法建立出來Worker對象以後,這個方法的最後會執行Worker內部thread屬性的start方法,而這個thread屬性實際上就是封裝了Worker的Thread,執行他的start方法實際上執行的是Worker的run方法,由於Worker是實現了Runnable接口的,在run方法裏面就會執行runWorker方法,而runWorker方法裏面首先會判斷當前咱們傳入的任務是否爲空,不爲空的話直接就會執行咱們經過execute或者submit方法提交的任務啦,注意一點就是咱們雖然會經過submit方法提交實現了Callable接口的對象,可是在調用submit方法的時候,實際上是會將Callable對象封裝成實現了Runnable接口對象的,不信咱們看看submit方法源碼是怎麼實現的:
public <T> Future<T> submit(Callable<T> task) { if (task == null) throw new NullPointerException(); RunnableFuture<T> ftask = newTaskFor(task); execute(ftask); return ftask; }
看到沒有呢,實際上在你傳入實現了Callable接口對象的時候,在submit方法裏面是會將其封裝成RunnableFuture對象的,而RunnableFuture接口是繼承了Runnable接口的;那麼說白了其實就是直接執行咱們提交任務的run方法了;若是爲空的話,則會經過getTask方法從阻塞隊列裏面拿出一個任務去執行;在任務執行結束以後繼續從阻塞隊列裏面拿任務,直到getTask的返回值爲空則退出runWorker內部循環,那麼什麼狀況下getTask返回爲空呢?查看getTask方法的源碼註釋能夠知道:在Worker必須須要退出的狀況下getTask會返回空,具體什麼狀況下Worker會退出呢?(1):當Worker的數量超過maximumPoolSize的時候;(2):當線程池狀態爲Stop的時候;(3):當線程池狀態爲Shutdown而且阻塞隊列爲空的時候;(4):使用等待超時時間從阻塞隊列中拿數據,可是超時以後仍然沒有拿到數據;
若是runWorker方法退出了它裏面的循環,那麼就說明當前阻塞隊列裏面是沒有任務能夠執行的了,你能夠看到在runWorker方法內部的finally語句塊中執行了processWorkerExit方法,用來對Worker對象進行回收操做,這個方法會傳入一個參數表示須要刪除的Worker對象;在進行Worker回收的時候會調用tryTerminate方法來嘗試關閉線程池,在tryTerminate方法裏面會檢查是否有Worker在工做,檢查線程池的狀態,沒問題的話就會將當前線程池的狀態過渡到Tidying,以後調用terminated方法,將線程池狀態更新到Terminated;
從上面的分析中,咱們能夠看出線程池運行的4個階段:
(1):poolSize < corePoolSize,則直接建立新的線程(核心線程)來執行當前提交的任務;
(2):poolSize = corePoolSize,而且此時阻塞隊列沒有滿,那麼會將當前任務添加到阻塞隊列中,若是此時存在工做線程(非核心線程)的話,那麼會由工做線程來處理該阻塞隊列中的任務,若是此時工做線程數量爲0的話,那麼會建立一個工做線程(非核心線程)出來;
(3):poolSize = corePoolSize,而且此時阻塞隊列已經滿了,那麼會直接建立新的工做線程(非核心線程)來處理阻塞隊列中的任務;
(4):poolSize = maximumPoolSize,而且此時阻塞隊列也滿了的話,那麼會觸發拒絕機制,具體決絕策略採用的是什麼就要看咱們建立ThreadPoolExecutor的時候傳入的RejectExecutionHandler參數了;
接下來就是線程池是怎麼作到重用線程的呢?
我的認爲線程池裏面重用線程的工做是在getTask裏面實現的,在getTask裏面是存在兩個for死循環嵌套的,他會不斷的從阻塞對列裏面取出須要執行的任務,返回給咱們的runWorker方法裏面,而在runWorker方法裏面只要getTask返回的任務不是空就會執行該任務的run方法來處理它,這樣一直執行下去,直到getTask返回空爲止,此時的狀況就是阻塞隊列裏面沒有任務了,這樣一個線程處理完一個任務以後接着再處理阻塞隊列中的另外一個任務,固然在線程池中的不一樣線程是能夠併發處理阻塞隊列中的任務的,最後在阻塞隊列內部不存在任務的時候會去判斷是否須要回收Worker對象,其實Worker對象的個數就是線程池中線程的個數,至於什麼狀況才須要回收,上面已經說了,就是四種狀況了;
最後就是線程池是怎樣被關閉的呢?
涉及到線程池的關閉,須要用到兩個方法,shutdown和shutdownNow,他們都是位於ThreadPoolExecutor裏面的,對於shutdown的話,他會將線程池狀態切換成Shutdown,此時是不會影響對阻塞隊列中任務執行的,可是會拒絕執行新加進來的任務,同時會回收閒置的Worker;而shutdownNow方法會將線程池狀態切換成Stop,此時既不會再去處理阻塞隊列裏面的任務,也不會去處理新加進來的任務,同時會回收全部Worker;