引出執行緒池
執行緒是併發程式設計的基礎,前面的文章裡,我們的例項基本都是基於執行緒開發作為例項,並且都是使用的時候就建立一個執行緒。這種方式比較簡單,但是存在一個問題,那就是執行緒的數量問題。
假設有一個系統比較複雜,需要的執行緒數很多,如果都是採用這種方式來建立執行緒的話,那麼就會極大的消耗系統資源。首先是因為執行緒本身的建立和銷燬需要時間,如果每個小任務都建立一個執行緒,那麼就會大大降低系統的效率。其次是執行緒本身也是佔用記憶體空間的,大量的執行緒執行會搶佔記憶體資源,處理不當很可能會記憶體溢位,這顯然不是我們想看到的。
那麼有什麼辦法解決呢?有一個好的思路就是對執行緒進行復用,因為所有的執行緒並不都是同一時間一起執行的,有些執行緒在某個時刻可能是空閒狀態,如果這部分空閒執行緒能有效利用起來,那麼就能讓執行緒的執行被充分的利用,這樣就不需要建立那麼多的執行緒了。我們可以把特定數量的執行緒放在一個容器裡,需要使用執行緒時,從容器裡拿出空閒執行緒使用,執行緒工作完後不急著關閉,而是退回到執行緒池等待使用。這樣的容器一般被稱為執行緒池。用執行緒池來管理執行緒是非常有效的方法,用一張圖片可以簡單的展示出執行緒池的管理流程:
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<Runnable> workQueue) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), defaultHandler);
}
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
threadFactory, defaultHandler);
}
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
RejectedExecutionHandler handler) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), handler);
}
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;
}
複製程式碼
可以看出,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 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;
// Packing and unpacking ctl
private static int runStateOf(int c) { return c & ~CAPACITY; }
private static int workerCountOf(int c) { return c & CAPACITY; }
private static int ctlOf(int rs, int wc) { return rs | 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(Runnable command) {
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建立一個新執行緒
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
/*
* 如果執行到這裡,有兩種情況:
* 1. 執行緒池已經不是RUNNING狀態;
* 2. 執行緒池是RUNNING狀態,但workerCount >= corePoolSize並且workQueue已滿。
* 這時,再次呼叫addWorker方法,但第二個引數傳入為false,將執行緒池的有限執行緒數量的上限設定為maximumPoolSize;
* 如果失敗則拒絕該任務
*/
else if (!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()))
return false;
for (;;) {
int wc = workerCountOf(c);
/* 這裡可以看出core引數決定著活動執行緒數的大小比較物件
* core為true表示與 corePoolSize大小進行比較
* core為false表示與 maximumPoolSize大小進行比較
* 當前活動執行緒數大於比較物件就返回false
*/
if (wc >= CAPACITY ||
wc >= (core ? corePoolSize : maximumPoolSize))
return false;
// 嘗試增加workerCount,如果成功,則跳出第一個for迴圈
if (compareAndIncrementWorkerCount(c))
break retry;
// 如果增加workerCount失敗,則重新獲取ctl的值
c = ctl.get(); // Re-read ctl
// 如果當前的執行狀態不等於rs,說明狀態已被改變,返回第一個for迴圈繼續執行
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 {
//建立一個worker物件w
w = new Worker(firstTask);
//例項化w的執行緒t
final Thread t = w.thread;
if (t != null) {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
// Recheck while holding lock.
// Back out on ThreadFactory failure or if
// 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);
}
return workerStarted;
}
複製程式碼
從程式碼中可以看出,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. Null if factory 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 (null if none)
*/
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 void run() {
runWorker(this);
}
// Lock methods
//
// The value 0 represents the unlocked state.
// The value 1 represents the locked state.
protected boolean isHeldExclusively() {
return getState() != 0;
}
protected boolean tryAcquire(int unused) {
if (compareAndSetState(0, 1)) {
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
return false;
}
protected boolean tryRelease(int unused) {
setExclusiveOwnerThread(null);
setState(0);
return true;
}
public void lock() { acquire(1); }
public boolean tryLock() { return tryAcquire(1); }
public void unlock() { release(1); }
public boolean isLocked() { return isHeldExclusively(); }
void interruptIfStarted() {
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;
// 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 {
//用於記錄任務執行前需要做哪些事,屬於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 Runnable getTask() {
//記錄上次從佇列獲取任務是否超時
boolean timedOut = false; // Did the last poll() time out?
for (;;) {
int c = ctl.get();
int rs = runStateOf(c);
// Check if queue empty only if necessary.
if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
//將workerCount減1
decrementWorkerCount();
return null;
}
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))
return null;
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)
return r;
//r為null,說明time為true,超時了,把timedOut也設定為true
timedOut = 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數量減1
if (completedAbruptly) // If abrupt, then workerCount 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 void shutdown() {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
checkShutdownAccess();
advanceRunState(SHUTDOWN);
interruptIdleWorkers();
onShutdown(); // hook for ScheduledThreadPoolExecutor
} finally {
mainLock.unlock();
}
tryTerminate();
}
public List<Runnable> shutdownNow() {
List<Runnable> tasks;
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
checkShutdownAccess();
advanceRunState(STOP);
interruptWorkers();
tasks = drainQueue();
} finally {
mainLock.unlock();
}
tryTerminate();
return tasks;
}
複製程式碼
程式碼邏輯就不一一進行解析了,總結一下兩個方法的特點就是:
- shutdown():不會立即終止執行緒池,而是要等所有任務快取佇列中的任務都執行完後才終止,但再也不會接受新的任務
- shutdownNow():立即終止執行緒池,並嘗試打斷正在執行的任務,並且清空任務快取佇列,返回尚未執行的任務
ThreadPoolExecutor建立執行緒池例項
ThreadPoolExecutor的執行機制講完了,接下來展示一下如何用ThreadPoolExecutor建立執行緒池例項,具體程式碼如下:
public static void main(String[] args) {
ExecutorService service = new ThreadPoolExecutor(5, 10, 300, TimeUnit.MILLISECONDS,
new ArrayBlockingQueue<Runnable>(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:高併發程式設計》