引出線程池
線程是并發編程的基礎,前面的文章里,我們的實例基本都是基于線程開發作為實例,并且都是使用的時候就創建一個線程。這種方式比較簡單,但是存在一個問題,那就是線程的數量問題。
假設有一個系統比較復雜,需要的線程數很多,如果都是采用這種方式來創建線程的話,那么就會極大的消耗系統資源。首先是因為線程本身的創建和銷毀需要時間,如果每個小任務都創建一個線程,那么就會大大降低系統的效率。其次是線程本身也是占用內存空間的,大量的線程運行會搶占內存資源,處理不當很可能會內存溢出,這顯然不是我們想看到的。
那么有什么辦法解決呢?有一個好的思路就是對線程進行復用,因為所有的線程并不都是同一時間一起運行的,有些線程在某個時刻可能是空閑狀態,如果這部分空閑線程能有效利用起來,那么就能讓線程的運行被充分的利用,這樣就不需要創建那么多的線程了。我們可以把特定數量的線程放在一個容器里,需要使用線程時,從容器里拿出空閑線程使用,線程工作完后不急著關閉,而是退回到線程池等待使用。這樣的容器一般被稱為線程池。用線程池來管理線程是非常有效的方法,用一張圖片可以簡單的展示出線程池的管理流程:
Executor框架
Java中也有一套框架來控制管理線程,那就是Executor框架。Executor框架是JDK1.5之后才引入的,位于java.util.cocurrent 包下,可以通過該框架來控制線程的啟動、執行和關閉,從而簡化并發編程的操作,這是它的核心成員類圖:
Executor:最上層的接口,定義了一個基本方法execute,接受一個Runnable參數,用來替代通常創建或啟動線程的方法。
ExecutorService:繼承自Executor接口,提供了處理多線程的方法。
ScheduledExecutorService:定時調度接口,繼承自ExecutorService。
AbstractExecutorService:執行框架的抽象類。
ThreadPoolExecutor:線程池中最核心的一個類,提供了線程池操作的基本方法。
Executors:線程池工廠類,可用于創建一系列有特定功能的線程池。
ThreadPoolExecutor詳解
以上Executor框架中的基本成員,其中最核心的的成員無疑就是ThreadPoolExecutor,想了解Java中線程池的運行機制,就必須先了解這個類,而最好的了解方式無疑就是看源碼。
構造函數
打開ThreadPoolExecutor的源碼,發現類中提供了四個構造方法
public ThreadPoolExecutor(int corePoolSize,? ? ? ? ? ? ? ? ? ? ? ? ? int maximumPoolSize,? ? ? ? ? ? ? ? ? ? ? ? ? long keepAliveTime,? ? ? ? ? ? ? ? ? ? ? ? ? TimeUnit unit,? ? ? ? ? ? ? ? ? ? ? ? ? BlockingQueue workQueue) {? ? this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,? ? ? ? Executors.defaultThreadFactory(), defaultHandler);}public ThreadPoolExecutor(int corePoolSize,? ? ? ? ? ? ? ? ? ? ? ? ? ? ? int maximumPoolSize,? ? ? ? ? ? ? ? ? ? ? ? ? ? ? long keepAliveTime,? ? ? ? ? ? ? ? ? ? ? ? ? ? ? TimeUnit unit,? ? ? ? ? ? ? ? ? ? ? ? ? ? ? BlockingQueue workQueue,? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ThreadFactory threadFactory) {? ? ? ? this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,? ? ? ? ? ? threadFactory, defaultHandler);}public ThreadPoolExecutor(int corePoolSize,? ? ? ? ? ? ? ? ? ? ? ? ? ? int maximumPoolSize,? ? ? ? ? ? ? ? ? ? ? ? ? ? long keepAliveTime,? ? ? ? ? ? ? ? ? ? ? ? ? ? TimeUnit unit,? ? ? ? ? ? ? ? ? ? ? ? ? ? BlockingQueue workQueue,? ? ? ? ? ? ? ? ? ? ? ? ? ? RejectedExecutionHandler handler) {? ? ? ? this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,? ? ? ? Executors.defaultThreadFactory(), handler);}public ThreadPoolExecutor(int corePoolSize,? ? ? ? ? ? ? ? ? ? ? ? ? ? ? int maximumPoolSize,? ? ? ? ? ? ? ? ? ? ? ? ? ? ? long keepAliveTime,? ? ? ? ? ? ? ? ? ? ? ? ? ? ? TimeUnit unit,? ? ? ? ? ? ? ? ? ? ? ? ? ? ? BlockingQueue 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;? ? }復制代碼
可以看出,ThreadPoolExecutor的構造函數中的參數還是比較多的,并且最核心的是第四個構造函數,其中完成了底層的初始化工作。
下面解釋一下構造函數參數的含義:
corePoolSize:線程池的基本大小。當提交一個任務到線程池后,線程池會創建一個線程執行任務,重復這種操作,直到線程池中的數目達到corePoolSize后不再創建新線程,而是把任務放到緩存隊列中。
maximumPoolSize:線程池允許創建的最大線程數。
workQueue:阻塞隊列,用于存儲等待執行的任務,并且只能存儲調用execute方法提交的任務。常用的有三種隊列,SynchronousQueue,LinkedBlockingDeque,ArrayBlockingQueue。
keepAliveTime:線程池中線程的最大空閑時間,這種情況一般是線程數目大于任務的數量導致。
unit:keepAliveTime的時間單位,TimeUnit是一個枚舉類型,位于java.util.concurrent包下。
threadFactory:線程工廠,用于創建線程。
handler:拒絕策略,當任務太多來不及處理時所采用的處理策略。
重要的變量
看完了構造函數,我們來看下ThreadPoolExecutor類中幾個重要的成員變量:
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));private static final int COUNT_BITS = Integer.SIZE - 3;private static final int CAPACITY? = (1 << COUNT_BITS) - 1;// runState is storedinthe high-order bitsprivate 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;// Packing and unpacking ctlprivate static int runStateOf(int c)? ? {returnc & ~CAPACITY; }private static int workerCountOf(int c)? {returnc & CAPACITY; }private static int ctlOf(int rs, int wc) {returnrs | wc; }復制代碼
ctl:控制線程運行狀態的一個字段。同時,根據下面的幾個方法runStateOf,workerCountOf,ctlOf可以看出,該字段還包含了兩部分的信息:線程池的運行狀態 (runState) 和線程池內有效線程的數量 (workerCount),并且使用的是Integar類型,高3位保存runState,低29位保存workerCount。
COUNT_BITS:值為29的常量,在字段CAPACITY被引用計算。
CAPACITY:表示有效線程數量(workerCount)的上限,大小為 (1<<29) - 1。
下面5個變量表示的是線程的運行狀態,分別是:
RUNNING:接受新提交的任務,并且能處理阻塞隊列中的任務;
SHUTDOWN:不接受新的任務,但會執行隊列中的任務。
STOP:不接受新任務,也不處理隊列中的任務,同時中斷正在處理任務的線程。
TIDYING:如果所有的任務都已終止了,workerCount (有效線程數) 為0,線程池進入該狀態后會調用 terminated() 方法進入TERMINATED 狀態。
TERMINATED:terminated( ) 方法執行完畢。
用一個狀態轉換圖表示大概如下? (圖片來源于www.cnblogs.com/liuzhihu/p/…):
構造函數和基本參數都了解后,接下來就是對類中重要方法的研究了。
線程池執行流程
execute方法
ThreadPoolExecutor類的核心調度方法是execute(),通過調用這個方法可以向線程池提交一個任務,交由線程池去執行。而ThreadPoolExecutor的工作邏輯也可以藉由這個方法來一步步理清。這是方法的源碼:
public void execute(Runnablecommand) {if(command== null)? ? ? ? throw new NullPointerException();? ? //獲取ctl的值,前面說了,該值記錄著runState和workerCount? ? int c = ctl.get();? ? /*? ? * 調用workerCountOf得到當前活動的線程數;? ? * 當前活動線程數小于corePoolSize,新建一個線程放入線程池中;? ? * addWorker(): 把任務添加到該線程中。? ? */if(workerCountOf(c) < corePoolSize) {if(addWorker(command,true))return;? ? ? ? //如果上面的添加線程操作失敗,重新獲取ctl值? ? ? ? c = ctl.get();? ? }? ? //如果當前線程池是運行狀態,并且往工作隊列中添加該任務if(isRunning(c) && workQueue.offer(command)) {? ? ? ? int recheck = ctl.get();? ? ? ? /*? ? ? ? * 如果當前線程不是運行狀態,把任務從隊列中移除? ? ? ? * 調用reject(內部調用handler)拒絕接受任務? ? ? ? */if(! isRunning(recheck) && remove(command))? ? ? ? ? ? reject(command);? ? ? ? //獲取線程池中的有效線程數,如果為0,則執行addWorker創建一個新線程elseif(workerCountOf(recheck) == 0)? ? ? ? ? ? addWorker(null,false);? ? }? ? /*? ? * 如果執行到這里,有兩種情況:? ? * 1. 線程池已經不是RUNNING狀態;? ? * 2. 線程池是RUNNING狀態,但workerCount >= corePoolSize并且workQueue已滿。? ? * 這時,再次調用addWorker方法,但第二個參數傳入為false,將線程池的有限線程數量的上限設置為maximumPoolSize;? ? * 如果失敗則拒絕該任務? ? */elseif(!addWorker(command,false))? ? ? ? reject(command);}復制代碼
簡單概括一下代碼的邏輯,大概是這樣:
1、判斷當前運行中的線程數是否小于corePoolSize,是的話則調用addWorker創建線程執行任務。
2、不滿足1的條件,就把任務放入工作隊列workQueue中。
3、如果任務成功加入workQueue,判斷線程池是否是運行狀態,不是的話先把任務移出工作隊列,并調用reject方法,使用拒絕策略拒絕該任務。線程如果是非運行中,調用addWorker創建一個新線程。
4、如果放入workQueue失敗 (隊列已滿),則調用addWorker創建線程執行任務,如果這時創建線程失敗 (addWorker傳進去的第二個參數值是false,說明這種情況是當前線程數不小于maximumPoolSize),就會調用reject(內部調用handler)拒絕接受任務。
整個執行流程用一張圖片表示大致如下:
以上就是execute方法的大概邏輯,接下來看看addWorker的方法實現。
addWorker方法
源碼如下:
private boolean addWorker(Runnable firstTask, boolean core) {? ? retry:for(;;) {? ? ? ? int c = ctl.get();? ? ? ? int rs = runStateOf(c);? ? ? ? /**線程池狀態不為SHUTDOWN時? ? ? ? * 判斷隊列或者任務是否為空,是的話返回false*/.if(rs >= SHUTDOWN &&? ? ? ? ? ? ! (rs == SHUTDOWN &&? ? ? ? ? ? ? firstTask == null &&? ? ? ? ? ? ? ! workQueue.isEmpty()))returnfalse;for(;;) {? ? ? ? ? ? int wc = workerCountOf(c);? ? ? ? ? ? /*這里可以看出core參數決定著活動線程數的大小比較對象? ? ? ? ? ? *? core為true表示與 corePoolSize大小進行比較? ? ? ? ? ? *? core為false表示與 maximumPoolSize大小進行比較? ? ? ? ? *? 當前活動線程數大于比較對象就返回false*/if(wc >= CAPACITY ||? ? ? ? ? ? ? ? wc >= (core ? corePoolSize : maximumPoolSize))returnfalse;? ? ? ? ? ? // 嘗試增加workerCount,如果成功,則跳出第一個for循環if(compareAndIncrementWorkerCount(c))breakretry;? ? ? ? ? ? // 如果增加workerCount失敗,則重新獲取ctl的值? ? ? ? ? ? c = ctl.get();? // Re-read ctl? ? ? ? ? ? // 如果當前的運行狀態不等于rs,說明狀態已被改變,返回第一個for循環繼續執行if(runStateOf(c) != rs)continueretry;? ? ? ? ? ? //elseCAS failed due to workerCount change; retry inner loop? ? ? ? }? ? }? ? boolean workerStarted =false;? ? boolean workerAdded =false;? ? Worker w = null;? ? try {? ? //創建一個worker對象w? ? ? ? w = new Worker(firstTask);? ? ? ? //實例化w的線程t? ? ? ? final Thread t = w.thread;if(t != null) {? ? ? ? ? ? final ReentrantLock mainLock = this.mainLock;? ? ? ? ? ? mainLock.lock();? ? ? ? ? ? try {? ? ? ? ? ? ? ? // Recheckwhileholding lock.? ? ? ? ? ? ? ? // Back out on ThreadFactory failure orif// shut down before lock acquired.? ? ? ? ? ? ? ? int rs = runStateOf(ctl.get());if(rs < SHUTDOWN ||? ? ? ? ? ? ? ? ? ? (rs == SHUTDOWN && firstTask == null)) {if(t.isAlive()) // precheck that t is startable? ? ? ? ? ? ? ? ? ? ? ? throw new IllegalThreadStateException();? ? ? ? ? ? ? ? ? ? // workers是一個HashSet,保存著任務的worker對象? ? ? ? ? ? ? ? ? ? workers.add(w);? ? ? ? ? ? ? ? ? ? int s = workers.size();if(s > largestPoolSize)? ? ? ? ? ? ? ? ? ? ? ? largestPoolSize = s;? ? ? ? ? ? ? ? ? ? workerAdded =true;? ? ? ? ? ? ? ? }? ? ? ? ? ? } finally {? ? ? ? ? ? ? ? mainLock.unlock();? ? ? ? ? ? }if(workerAdded) {? ? ? ? ? ? //啟動線程? ? ? ? ? ? ? ? t.start();? ? ? ? ? ? ? ? workerStarted =true;? ? ? ? ? ? }? ? ? ? }? ? } finally {if(! workerStarted)? ? ? ? ? ? addWorkerFailed(w);? ? }returnworkerStarted;}復制代碼
從代碼中可以看出,addWorker方法的主要工作是在線程池中創建一個新的線程并執行,其中firstTask參數指定的是新線程需要執行的第一個任務,core參數決定于活動線程數的比較對象是corePoolSize還是maximumPoolSize。根據傳進來的參數首先對線程池和隊列的狀態進行判斷,滿足條件就新建一個Worker對象,并實例化該對象的線程,最后啟動線程。
Worker類
根據addWorker源碼中的邏輯,我們可以發現,線程池中的每一個線程其實都是對應的Worker對象在維護的,所以我們有必要對Worker類一探究竟,先看一下類的源碼:
private final class Worker? ? extends AbstractQueuedSynchronizer? ? implements Runnable{? ? /**? ? * This class will never be serialized, but we provide a? ? * serialVersionUID to suppress a javac warning.? ? */? ? private static final long serialVersionUID = 6138294804551838833L;? ? /** Thread this worker is running in.? Nulliffactory fails. */? ? final Thread thread;? ? /** Initial task to run.? Possibly null. */? ? Runnable firstTask;? ? /** Per-thread task counter */? ? volatile long completedTasks;? ? /**? ? * Creates with given first task and thread from ThreadFactory.? ? * @param firstTask the first task (nullifnone)? ? */? ? Worker(Runnable firstTask) {setState(-1); // inhibit interrupts until runWorker? ? ? ? this.firstTask = firstTask;? ? ? ? this.thread = getThreadFactory().newThread(this);? ? }? ? /** Delegates main run loop to outer runWorker? */? ? public voidrun() {? ? ? ? runWorker(this);? ? }? ? // Lock methods? ? //? ? // The value 0 represents the unlocked state.? ? // The value 1 represents the locked state.? ? protected booleanisHeldExclusively() {returngetState() != 0;? ? }? ? protected boolean tryAcquire(int unused) {if(compareAndSetState(0, 1)) {setExclusiveOwnerThread(Thread.currentThread());returntrue;? ? ? ? }returnfalse;? ? }? ? protected boolean tryRelease(int unused) {setExclusiveOwnerThread(null);setState(0);returntrue;? ? }? ? public voidlock()? ? ? ? { acquire(1); }? ? public booleantryLock()? {returntryAcquire(1); }? ? public voidunlock()? ? ? { release(1); }? ? public booleanisLocked() {returnisHeldExclusively(); }? ? voidinterruptIfStarted() {? ? ? ? Thread t;if(getState() >= 0 && (t = thread) != null && !t.isInterrupted()) {? ? ? ? ? ? try {? ? ? ? ? ? ? ? t.interrupt();? ? ? ? ? ? } catch (SecurityException ignore) {? ? ? ? ? ? }? ? ? ? }? ? }}復制代碼
從Worker類的構造函數可以看出,當實例化一個Worker對象時,Worker對象會把傳進來的Runnable參數firstTask賦值給自己的同名屬性,并且用線程工廠也就是當前的ThreadFactory來新建一個線程。
同時,因為Worker實現了Runnable接口,所以當Worker類中的線程啟動時,調用的其實是run()方法。run方法中調用的是runWorker方法,我們來看下它的具體實現:
final void runWorker(Worker w) {? ? Thread wt = Thread.currentThread();? ? //獲取第一個任務? ? Runnable task = w.firstTask;? ? w.firstTask = null;? ? //允許中斷? ? w.unlock(); // allow interrupts? ? //是否因為異常退出循環的標志,processWorkerExit方法會對該參數做判斷? ? boolean completedAbruptly =true;? ? try {? ? //判斷task是否為null,是的話通過getTask()從隊列中獲取任務while(task != null || (task = getTask()) != null) {? ? ? ? ? ? w.lock();? ? ? ? ? ? // If pool is stopping, ensure thread is interrupted;? ? ? ? ? ? //ifnot, ensure thread is not interrupted.? This? ? ? ? ? ? // requires a recheckinsecondcaseto deal with? ? ? ? ? ? // shutdownNow racewhileclearing interrupt? ? ? ? ? ? /* 這里的判斷主要邏輯是這樣:? ? ? ? ? ? * 如果線程池正在停止,那么就確保當前線程是中斷狀態;? ? ? ? ? ? * 如果不是的話,就要保證不是中斷狀態? ? ? ? ? ? */if((runStateAtLeast(ctl.get(), STOP) ||? ? ? ? ? ? ? ? (Thread.interrupted() &&? ? ? ? ? ? ? ? ? runStateAtLeast(ctl.get(), STOP))) &&? ? ? ? ? ? ? ? !wt.isInterrupted())? ? ? ? ? ? ? ? wt.interrupt();? ? ? ? ? ? try {? ? ? ? ? ? ? ? //用于記錄任務執行前需要做哪些事,屬于ThreadPoolExecutor類中的方法,//是空的,需要子類具體實現? ? ? ? ? ? ? ? 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);? ? }}復制代碼
總結一下runWorker方法的運行邏輯:
1、通過while循環不斷地通過getTask()方法從隊列中獲取任務;
2、如果線程池正在停止狀態,確保當前的線程是中斷狀態,否則確保當前線程不中斷;
3、調用task的run()方法執行任務,執行完畢后需要置為null;
4、循環調用getTask()取不到任務了,跳出循環,執行processWorkerExit()方法。
過完runWorker()的運行流程,我們來看下getTask()是怎么實現的。
getTask方法
getTask()方法的作用是從隊列中獲取任務,下面是該方法的源碼:
private RunnablegetTask() {//記錄上次從隊列獲取任務是否超時? ? boolean timedOut =false; // Did the last poll() time out?for(;;) {? ? ? ? int c = ctl.get();? ? ? ? int rs = runStateOf(c);? ? ? ? // Checkifqueue empty onlyifnecessary.if(rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {? ? ? ? //將workerCount減1? ? ? ? ? ? decrementWorkerCount();returnnull;? ? ? ? }? ? ? ? int wc = workerCountOf(c);? ? ? ? // Are workers subject to culling?? ? ? ? /*timed變量用于判斷線程的操作是否需要進行超時判斷? ? ? ? *allowCoreThreadTimeOut不管它,默認是false*? wc > corePoolSize,當前線程是如果大于核心線程數corePoolSize? ? ? ? */? ? ? ? boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;if((wc > maximumPoolSize || (timed && timedOut))? ? ? ? ? ? && (wc > 1 || workQueue.isEmpty())) {if(compareAndDecrementWorkerCount(c))returnnull;continue;? ? ? ? }? ? ? ? try {? ? ? ? /* 根據timed變量判斷,如果為true,調用workQueue的poll方法獲取任務,? ? ? ? * 如果在keepAliveTime時間內沒有獲取到任務,則返回null;? ? ? ? * timed為false的話,就調用workQueue的take方法阻塞隊列,? ? ? ? ? ? ? ? ? * 直到隊列中有任務可取。? ? ? ? */? ? ? ? ? ? Runnable r = timed ?? ? ? ? ? ? ? ? workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :? ? ? ? ? ? ? ? workQueue.take();if(r != null)returnr;? ? ? ? ? ? //r為null,說明time為true,超時了,把timedOut也設置為truetimedOut =true;? ? ? ? } catch (InterruptedException retry) {? ? ? ? //發生異常,把timedOut也設置為false,重新跑循環? ? ? ? ? ? timedOut =false;? ? ? ? }? ? }}復制代碼
getTask的代碼看上去比較簡單,但其實內有乾坤,我們來重點分析一下兩個if判斷的邏輯:
1、當進入getTask方法后,先判斷當前線程池狀態,如果線程池狀態rs >= SHUTDOWN,再進行以下判斷:
1)rs 的狀態是否大于STOP;2)隊列是否為空;
滿足以上條件其中之一,就將workerCount減1并返回null,也就是表示隊列中不再有任務。因為線程池的狀態值是SHUTDOWN以上時,隊列中不再允許添加新任務,所以上面兩個條件滿足一個都說明隊列中的任務都取完了。
2、進入第二個if判斷,這里的邏輯有點繞,但作用比較重要,是為了控制線程池的有效線程數量,我們來具體解析下代碼:
wc > maximumPoolSize:判斷當前線程數是否大于maximumPoolSize,這種情況一般很少發生,除非是maximumPoolSize的大小在該程序執行的同時被進行設置,比如調用ThreadPoolExecutor中的setMaximumPoolSize方法。
timed && timedOut:如果為true,表示當前的操作需要進行超時判斷,并且上次從隊列獲取任務已經超時。
wc > 1 || workQueue.isEmpty():如果工作線程大于1,或者阻塞隊列是空的。
compareAndDecrementWorkerCount:比較并將線程池中的workerCount減1
在上文中,我們解析execute方法的邏輯時了解到,如果當前線程池的線程數量超過了corePoolSize且小于maximumPoolSize,并且workQueue已滿時,仍然可以增加工作線程。
但調用getTask()取任務的過程中,如果超時沒有獲取到任務,也就是timedOut為true的情況,說明workQueue已經為空了,也就說明了當前線程池中不需要那么多線程來執行任務了,可以把多于corePoolSize數量的線程銷毀掉,也就是不斷的讓任務被取出,讓線程數量保持在corePoolSize即可,直到getTask方法返回null。
而當getTask方法返回null后,runWorker方法中就會因為取不到任務而執行processWorkerExit()方法。
processWorkerExit方法
processWorkerExit方法的作用主要是對worker對象的移除,下面是方法的源碼:
private void processWorkerExit(Worker w, boolean completedAbruptly) {//是異常退出的話,執行程序將workerCount數量減1if(completedAbruptly) // If abrupt,thenworkerCount wasn't adjusted
? ? ? ? decrementWorkerCount();
? ? final ReentrantLock mainLock = this.mainLock;
? ? mainLock.lock();
? ? try {
? ? ? ? completedTaskCount += w.completedTasks;
? ? ? ? // 從workers的集合中移除worker對象,也就表示著從線程池中移除了一個工作線程
? ? ? ? workers.remove(w);
? ? } finally {
? ? ? ? mainLock.unlock();
? ? }
? ? tryTerminate();
? ? int c = ctl.get();
? ? if (runStateLessThan(c, STOP)) {
? ? ? ? if (!completedAbruptly) {
? ? ? ? ? ? int min = allowCoreThreadTimeOut ? 0 : corePoolSize;
? ? ? ? ? ? if (min == 0 && ! workQueue.isEmpty())
? ? ? ? ? ? ? ? min = 1;
? ? ? ? ? ? if (workerCountOf(c) >= min)
? ? ? ? ? ? ? ? return; // replacement not needed
? ? ? ? }
? ? ? ? addWorker(null, false);
? ? }
}復制代碼
至此,從executor方法開始的整個運行過程就完畢了,總結一下該流程:
執行executor? -->? 新建Worker對象,并實例化線程? -->? 調用runWorker方法,通過getTask()獲取任務,并執行run方法? -->? getTask()方法中不斷向隊列取任務,并將workerCount數量減1,直至返回null? -->? 調用processWorkerExit清除worker對象。
用一張流程圖表示如下所示 (圖片來源于www.cnblogs.com/liuzhihu/p/…):
任務隊列workQueue
前面我們多次提到了workQueue,這是一個任務隊列,用來存放等待執行的任務,它是BlockingQueue類型的對象,而在ThreadPoolExecutor的源碼注釋中,詳細介紹了三種常用的Queue類型,分別是:
SynchronousQueue:直接提交的隊列。這個隊列沒有容量,當接收到任務的時候,會直接提交給線程處理,而不保留它。如果沒有空閑的線程,就新建一個線程來處理這個任務!如果線程數量達到最大值,就會執行拒絕策略。所以,使用這個類型隊列的時候,一般都是將maximumPoolSize一般指定成Integer.MAX_VALUE,避免容易被拒絕。
ArrayBlockingQueue:有界的任務隊列。需要給定一個參數來限制隊列的長度,接收到任務的時候,如果沒有達到corePoolSize的值,則新建線程 (核心線程) 執行任務,如果達到了,則將任務放入等待隊列。如果隊列已滿,則在總線程數不到maximumPoolSize的前提下新建線程執行任務,若大于maximumPoolSize,則執行拒絕策略。
LinkedBlockingQueue:無界的任務隊列。該隊列沒有任務數量的限制,所以任務可以一直入隊,知道耗盡系統資源。當接收任務,如果當前線程數小于corePoolSize,則新建線程處理任務;如果當前線程數等于corePoolSize,則進入隊列等待。
任務拒絕策略
當線程池的任務隊列已滿并且線程數目達到maximumPoolSize時,對于新加的任務一般會采取拒絕策略,通常有以下四種策略:
AbortPolicy:直接拋出異常,這是默認策略;
CallerRunsPolicy:用調用者所在的線程來執行任務;
DiscardOldestPolicy:丟棄阻塞隊列中靠最前的任務,并執行當前任務;
DiscardPolicy:直接丟棄任務;
線程池的關閉
ThreadPoolExecutor提供了兩個方法,用于線程池的關閉,分別是shutdown()和shutdownNow():
public voidshutdown() {? ? final ReentrantLock mainLock = this.mainLock;? ? mainLock.lock();? ? try {? ? ? ? checkShutdownAccess();? ? ? ? advanceRunState(SHUTDOWN);? ? ? ? interruptIdleWorkers();? ? ? ? onShutdown(); // hookforScheduledThreadPoolExecutor? ? } finally {? ? ? ? mainLock.unlock();? ? }? ? tryTerminate();}public ListshutdownNow() {? ? ? ? List tasks;? ? ? ? final ReentrantLock mainLock = this.mainLock;? ? ? ? mainLock.lock();? ? ? ? try {? ? ? ? ? ? checkShutdownAccess();? ? ? ? ? ? advanceRunState(STOP);? ? ? ? ? ? interruptWorkers();? ? ? ? ? ? tasks = drainQueue();? ? ? ? } finally {? ? ? ? ? ? mainLock.unlock();? ? ? ? }? ? ? ? tryTerminate();returntasks;? ? }復制代碼
代碼邏輯就不一一進行解析了,總結一下兩個方法的特點就是:
shutdown():不會立即終止線程池,而是要等所有任務緩存隊列中的任務都執行完后才終止,但再也不會接受新的任務
shutdownNow():立即終止線程池,并嘗試打斷正在執行的任務,并且清空任務緩存隊列,返回尚未執行的任務
ThreadPoolExecutor創建線程池實例
ThreadPoolExecutor的運行機制講完了,接下來展示一下如何用ThreadPoolExecutor創建線程池實例,具體代碼如下:
public static void main(String[] args) {? ? ExecutorService service = new ThreadPoolExecutor(5, 10, 300, TimeUnit.MILLISECONDS,? ? ? ? ? ? new ArrayBlockingQueue(5));? ? //用lambda表達式編寫方法體中的邏輯? ? Runnable run = () -> {? ? ? ? try {? ? ? ? ? ? Thread.sleep(1000);? ? ? ? ? ? System.out.println(Thread.currentThread().getName() +"正在執行");? ? ? ? } catch (InterruptedException e) {? ? ? ? ? ? e.printStackTrace();? ? ? ? }? ? };for(int i = 0; i < 10; i++) {? ? ? ? service.execute(run);? ? }? ? //這里一定要做關閉? ? service.shutdown();}復制代碼
上面的代碼中,我們創建的ThreadPoolExecutor線程池的核心線程數為5個,所以,當調用線程池執行任務時,同時運行的線程最多也是5個,執行main方法,輸出結果如下:
pool-1-thread-3正在執行pool-1-thread-1正在執行pool-1-thread-4正在執行pool-1-thread-5正在執行pool-1-thread-3正在執行pool-1-thread-2正在執行pool-1-thread-1正在執行pool-1-thread-4正在執行pool-1-thread-5正在執行復制代碼
看到出來,線程池確實只有5個線程在工作,也就是真正的實現了線程的復用,說明我們的ThreadPoolExecutor實例是有效的。
參考:
《實戰Java:高并發程序設計》
現在加群即可獲取詳細的Java架構腦圖,還有Java工程化、高性能及分布式、高性能、高架構、zookeeper、性能調優、Spring、MyBatis、Netty源碼分析和大數據等多個知識點高級進階干貨的直播免費學習權限及相關視頻資料,群號:923116658
點擊鏈接加入群聊【Java架構解析】:https://jq.qq.com/?_wv=1027&k=5e1QsXb