導論
在開發中,我們會遇到需要多個線程執行的任務。如果我們每次都通過new Thread來創建線程執行任務的話,在線程很多的情況下,是會非常銷燬資源,影響程序運行的。Java提供了線程池Executor來幫助我們處理需要用到多個線程的情況,線程池可以用來存儲多個線程,通過創建線程池,我們可以有以下幾個好處:
- 重用線程池的線程,避免了因爲重複創建、銷燬線程而到來的性能開銷。
- 能夠控制線程池中的最大併發數,避免了線程間因爲互相搶佔系統資源而導致的阻塞現象。
- 能夠對線程進行簡單的管理,並且提供了定時執行、循環執行線程的功能。
下面我將詳細的介紹關於線程池的知識點
TreadPoolExecutor的參數說明
ThreadPoolExecutor是線程池的真實實現,它的構造方法中有一系列參數來設置線程池。
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
-
corePoolSize
指的是最大核心線程的數量。默認情況下,核心線程即使是在很長一段時間處於閒置狀態,也不會銷燬。但是當線程池設置了allowCoreThreadTimeOut參數爲true時,且核心線程閒置的時間超過了線程池中的keepAliveTime變量所指定的時長後,也會被終止。 -
maximumPoolSize
指的是最大線程數量。當線程池中的核心線程數達到最大後,且工作隊列也滿了後,線程池就會立即創建新線程來執行任務,而maximumPoolSize則指定了線程池中的最大線程數,當線程數達到了最大線程數,並且工作隊列中的任務也滿了,這時在添加任務時,會根據設置處理方式,對線程池進行處理。 -
keepAliveTime
指的是非核心線程的超時時間,當非核心線程閒置時間超過了這個之後,線程將會被回收。同時,當設置了allowCoreTreadTimeOut時,keepAliveTime也會作用核心線程。 -
unit
keepAliveTime的單位,這是一個枚舉值,常用的有TimeUnit.DAYS; //天 TimeUnit.HOURS; //小時 TimeUnit.MINUTES; //分鐘 TimeUnit.SECONDS; //秒 TimeUnit.MILLISECONDS; //毫秒 TimeUnit.MICROSECONDS; //微妙 TimeUnit.NANOSECONDS; //納秒
-
workQueue
任務隊列,當我們調用線程池中的execute方法提交的Runnable對象就會存儲在這裏,在被線程池中的線程給執行。常用的有-
ArrayBlockingQueue 是一個有界緩存等待隊列,可以指定緩存隊列的大小,當正在執行的線程數等於corePoolSize時,多餘的元素緩存在ArrayBlockingQueue隊列中等待有空閒的線程時繼續執行,當ArrayBlockingQueue已滿時,加入ArrayBlockingQueue失敗,會開啓新的線程去執行,當線程數已經達到最大的maximumPoolSizes時,再有新的元素嘗試加入ArrayBlockingQueue時,如果沒有設置handler時會報錯。
-
LinkedBlockingQueue 是一個無界緩存等待隊列。當前執行的線程數量達到corePoolSize的數量時,剩餘的元素會在阻塞隊列裏等待。(所以在使用此阻塞隊列時maximumPoolSizes就相當於無效了),每個線程完全獨立於其他線程。生產者和消費者使用獨立的鎖來控制數據的同步,即在高併發的情況下可以並行操作隊列中的數據。
-
SynchronousQueue SynchronousQueue沒有容量,是無緩衝等待隊列,是一個不存儲元素的阻塞隊列,會直接將任務交給消費者,必須等隊列中的添加元素被消費後才能繼續添加新的元素。擁有公平(FIFO)和非公平(LIFO)策略,非公平側羅會導致一些數據永遠無法被消費的情況?使用SynchronousQueue阻塞隊列一般要求maximumPoolSizes爲無界(Integer.MAX_VALUE),避免線程拒絕執行操作。
-
-
ThreadFactory
用於自定義創建線程的方式,是一個接口,裏面只有newThread方法。 -
handler
用來設置拒絕處理任務時的策略,這時候可能是任務隊列滿了,或者是無法成功的執行任務,常用的有- ThreadPoolExecutor.AbortPolicy 丟棄任務並拋出RejectedExecutionException異常。 默認值
- ThreadPoolExecutor.DiscardPolicy 也是丟棄任務,但是不拋出異常。
- ThreadPoolExecutor.DiscardOldestPolic 丟棄隊列最前面的任務,然後重新嘗試執行任務(重複此過程)
- ThreadPoolExecutor.CallerRunsPolicy 任務被拒絕添加後,會調用當前線程池的所在的線程去執行被拒絕的任務。
TreadPoolExecutor的源碼分析
- 線程池的狀態分析
//原子狀態控制數
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
//29比特位
private static final int COUNT_BITS = Integer.SIZE - 3;
//實際容量 2^29-1
private static final int CAPACITY = (1 << COUNT_BITS) - 1;
// runState is stored in the high-order bits
// runState存儲在高位中
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 打包和解壓ctl
// 解壓runState
private static int runStateOf(int c) { return c & ~CAPACITY; }
// 解壓workerCount
private static int workerCountOf(int c) { return c & CAPACITY; }
// 打包ctl
private static int ctlOf(int rs, int wc) { return rs | wc; }
線程池中採用AtomicInteger的clt來將workCount(工作線程數)和runState(運行狀態)壓縮在一起。其中用3個比特位來記錄runState,用29比特位來記錄workCount。所以從上面我們可以知道,實際上可以允許的最大線程數爲2^29-1。
接着我們來看下每個狀態的含義
狀態 | 含義 |
---|---|
RUNNING | 運行態,線程池可以接收任務,並處理任務 |
SHUTDOWN | 關閉態,不接收新任務,但會處理完隊列中的任務,調用了shutdown()方法會處於該狀態 |
STOP | 停止態,不接受新任務,並且不會處理隊列中的任務,打斷正在執行的任務,調用stop()方法會處於該狀態 |
TIYDING | 整理態,所有任務已經結束,workerCount = 0 ,將執行terminated()方法 |
TERMINATED | 結束態,terminated() 方法已完成 |
- 任務的執行
private final BlockingQueue<Runnable> workQueue; //任務緩存隊列,用來存放等待執行的任務
private final ReentrantLock mainLock = new ReentrantLock(); //線程池的主要狀態鎖,對線程池狀態(比如線程池大小
//、runState等)的改變都要使用這個鎖
private final HashSet<Worker> workers = new HashSet<Worker>(); //用來存放工作集
private volatile long keepAliveTime; //線程存貨時間
private volatile boolean allowCoreThreadTimeOut; //是否允許爲核心線程設置存活時間
private volatile int corePoolSize; //核心池的大小(即線程池中的線程數目大於這個參數時,提交的任務會被放進任務緩存隊列)
private volatile int maximumPoolSize; //線程池最大能容忍的線程數
private volatile int poolSize; //線程池中當前的線程數
private volatile RejectedExecutionHandler handler; //任務拒絕策略
private volatile ThreadFactory threadFactory; //線程工廠,用來創建線程
private int largestPoolSize; //用來記錄線程池中曾經出現過的最大線程數
private long completedTaskCount; //用來記錄已經執行完畢的任務個數
以上是一些比較重要的參數具體含義註釋中有介紹,就不多說了。
接着我們來看下ThreadLocalPool中最核心的方法execute(),這個方法是用於我們向線程池提交任務的:
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();
}
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);
}
else if (!addWorker(command, false))
reject(command);
}
這裏的ctl包含了runState和workCount的值,首先當線程池中的線程數小於corePoolSize時,就會通過addWorker來開啓個新線程,並利用新線程執行任務。同時addWorker會檢查運行狀態和線程數,如果添加失敗的化(可能是調用了shutdown或stop),會更新一下clt,防止在addWorker的過程中clt發生了改變。
當我們線程數大於核心線程數,或者沒有成功添加任務時,我們判斷線程池的運行狀態和工作隊列是否能夠添加任務(如果滿了則不行),如果滿足條件,我們仍然需要更新一下ctl的值,因爲可能在執行方法的過程中可能會有線程的銷燬、添加,或者調用了shutdown和stop方法。之後如果不是running狀態(即調用了shutdown()和stop()方法),我們將拒絕添加任務。當線程池處於RUNNING狀態,並且線程個數爲0時,也會調用addWorker創建線程。
上面的代碼核心時addWorker方法,我們來看看他是怎樣實現創建線程
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 {
// 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.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;
}
代碼有點長,但整體並不難理解,首先是一個死循環,在循環中會根據傳過來的參數判斷是否能夠創建一個新的worker,這裏的worker是一個實現了Runnable的類,創建線程就是在它裏面完成的,我們可以來看下它的構造函數:
//Worker
/** 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);
}
看到了嗎,在Worker中包含着線程,以及任務和完成任務數,並且在構造方式中,就會利用getThreadFactory()創建線程。可以說Worker就是線程池中執行線程的對象。既然Worker實現了Runnable那麼,我們就來看下它其中的run方法:
//Worker
public void run() {
runWorker(this);
}
final void runWorker(Worker w) {
Thread wt = Thread.currentThread();
Runnable task = w.firstTask;
w.firstTask = null;
w.unlock(); // allow interrupts
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);
}
}
可以看出runWorker方法首先會執行傳入的任務,然後再不停的調用getTask來獲取任務,這個方法是在ThreadPoolExecutor中的,那麼任務從哪裏獲取呢?當然是你傳入的任務緩存隊列啦。我們來看看getTask的方法
private Runnable getTask() {
boolean timedOut = false; // 判斷是否閒置實間超時了
for (;;) {
int c = ctl.get();
int rs = runStateOf(c);
// 在這裏當線程池的狀態不爲SHUTDOWN和RUNNING或者狀態爲SHUTDOWN並且任務隊列爲空時,返回null,代表了無法
//在接收任務了。
if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
decrementWorkerCount();
return null;
}
int wc = workerCountOf(c);
// Are workers subject to culling?
boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
//當線程池已經
if ((wc > maximumPoolSize || (timed && timedOut))
&& (wc > 1 || workQueue.isEmpty())) {
if (compareAndDecrementWorkerCount(c))
return null;
continue;
}
try {
Runnable r = timed ?
workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
workQueue.take();
if (r != null)
return r;
timedOut = true;
} catch (InterruptedException retry) {
timedOut = false;
}
}
}
上面關鍵的地方我都加了註釋,應該不會太難理解,就是不斷的從任務隊列中取出任務返回給worker處理。
好了我們返回addWorker方法中
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
}
}
在循環中,當線程池處於stop狀態或者特殊情況下工作隊列爲空時,返回false,即代表無法添加worker,並且在循環的內部還有一個循環,噹噹前的核心線程數大於規定的最大核心線程數或則,總線程數達到了最大值,則返回false,無法添加。否則的話,將會使stl中的線程數加一,並且跳出循環。
//addWorker
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 {
// 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.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;
之後嘗試創建worker,並且如果worker中利用threadFactory創建的線程不爲null的話,就會直接用這個線程執行任務。如何threadFactory創建線程失敗的話,同樣會導致返回false。
總結
通過上面的源碼分析,我們可以得到如下的結論:
- 當線程池中的線程數小於最大核心線程數時,每獲得一個任務,就會創建一個新的Thread去執行這個任務。
- 當線程數>=corePoolSize,每獲得一個任務時,會首先嚐試把任務放進緩存隊列中去,但是當存入緩存隊列失敗時(一般時隊列滿了),則會創建新的線程來執行這個任務。
- 如果當前線程池中的線程數目達到maximumPoolSize,並且任務隊列滿了,則會採取任務拒絕策略進行處理;
- 如果線程池中的線程數量大於 corePoolSize時,如果某線程空閒時間超過keepAliveTime,線程將被終止,直至線程池中的線程數目不大於corePoolSize;如果允許爲核心池中的線程設置存活時間,那麼核心池中的線程空閒時間超過keepAliveTime,線程也會被終止。
常用的線程池
一般情況下我們使用線程池不會直接調用它的構造方法來配置參數,而是調用線程池中的特定方法來獲得特定的線程池。常用的有以下四種
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
public static ScheduledExecutorService newScheduledThreadPool(
int corePoolSize, ThreadFactory threadFactory) {
return new ScheduledThreadPoolExecutor(corePoolSize, threadFactory);
}
public ScheduledThreadPoolExecutor(int corePoolSize,
ThreadFactory threadFactory) {
super(corePoolSize, Integer.MAX_VALUE,
DEFAULT_KEEPALIVE_MILLIS, MILLISECONDS,
new DelayedWorkQueue(), threadFactory);
}
-
FixedThreadPool
FixedThreadPool只會有數量固定的核心線程,也就意味着當所有線程都處於活動狀態時,新任務來時都會處於等待狀態,並且這些線程沒有超時機制,不會被回收,除非關閉線程池。可以看出構造時傳入了LinkedBlockingQueue,所以任務緩存隊列也是沒有大小限制的。 -
SingeleThreadPool
這個線程池中只有一個核心類,它確保了所有任務都在一個線程中按順序執行。它的好處就是這些任務之間不需要處理併發的問題。 -
CachedThreadPool
這個線程池中只有非核心線程,且最大線程數位Integer.MAX_VALUE,這就意味着它可以擁有大量的
線程。這裏的緩存隊列是SynchronousQueue,這個隊列很特殊,它不具有存儲功能,由於使用較少,這裏就不多說了。總的來說這種線程池適合於大量耗時較少的任務的情形。 -
ScheduledThreadPool
這個線程的核心線程數是固定的,而非核心線程數是沒有限制的,並且當被限制時會被立即回收。它能夠實現延遲任務以及週期性的任務。
參考 https://www.cnblogs.com/dolphin0520/p/3932921.html
《Android開發藝術探索》