執行緒池作用
相對於為每個請求都建立一個執行緒,執行緒池通過重用現有的執行緒而不是建立新執行緒,可以在處理多個請求時分攤線上程建立和銷燬過程中產生的巨大開銷,當請求到達時,工作執行緒通過已經存在,不會由於等待建立執行緒而延遲任務的執行,從而提高響應性。通過適當調整執行緒池的大小,可以建立足夠多的執行緒以便使處理器保持忙碌狀態,同時還可以防止過多執行緒相互競爭資源而使應用程式耗盡記憶體或失敗
執行緒池處理流程
1)判斷核心執行緒池裡的執行緒是否都在執行任務。如果不是,則建立一個新的工作執行緒來執行任務。如果核心執行緒池裡的執行緒都在執行任務,則進入下個流程
2)判斷工作佇列是否已經滿。如果工作佇列沒有滿,則將新提交的任務儲存在這個工作佇列裡。如果工作佇列滿了,則進入下個流程
3)判斷執行緒池的執行緒是否都處於工作狀態。如果沒有,則建立一個新的工作執行緒來執行任務。如果已經滿了,則交給飽和策略來處理這個任務
示意圖:
建立執行緒池
ThreadPoolExecutor構造方法:
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
... //程式碼省略
}
複製程式碼
一共七個引數:
①.AbortPolicy:直接丟擲異常,預設策略
②.CallerRunsPolicy:只用呼叫者所線上程來執行任務
③.DiscardOldestPolicy:丟棄阻塞佇列中靠最前的任務,並執行當前任務
④.DiscardPolicy:不處理,直接丟棄
也可以根據應用場景需要來實現RejectedExecutionHandler介面自定義策略
呼叫Exectors中的靜態工廠方法也可以來建立執行緒池
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue());
}
複製程式碼
建立一個固定長度的執行緒池,每當提交一個任務時就建立一個執行緒,直到達到執行緒池的最大數量(corePoolSize == maximumPoolSize),這時執行緒池的規模將不再變化(若某個執行緒由於發生了未預期的Exception而結束,執行緒池會補充一個新執行緒),使用LinkedBlockingQuene作為阻塞佇列,適用於負載比較重的伺服器
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue());
}
複製程式碼
建立一個可快取執行緒的執行緒池,預設快取60s,使用SynchronousQueue作為阻塞佇列(沒有資料快取空間的阻塞佇列,每一個put操作必須等待一個take操作,若任務提交的速度遠遠大於CachedThreadPool的處理速度,CachedThreadPool會不斷地建立新執行緒來執行任務,可能會導致系統耗盡CPU和記憶體資源)。適用於執行很多的短期非同步任務的小程式,或者負載較輕的伺服器,使用該執行緒池時,一定要注意控制併發的任務數,否則建立大量的執行緒可能導致嚴重的效能問題
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue()));
}
複製程式碼
單執行緒的Executor,執行緒池中只有一個執行緒,若執行緒異常結束,會建立另一個執行緒替代。newSingleThreadExecutor能確保依照任務在佇列中的順訊來序列執行,內部使用LinkedBlockingQueue作為阻塞佇列,適用於需要保證順序地執行各個任務;並且在任意時間點,不會有多個執行緒是活動的應用場景
可以延遲或定時的方式執行任務,適用於週期任務public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) { return new ScheduledThreadPoolExecutor(corePoolSize); }
複製程式碼public ScheduledThreadPoolExecutor(int corePoolSize) { super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS, new DelayedWorkQueue()); } 複製程式碼
複製程式碼
實現原理
執行緒池狀態
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;
複製程式碼
任務提交
有兩種方式向執行緒池提交任務,分別為execute()和submit()方法。execute()方法提交的任務不能獲取返回值,而submit()方法提交的任務會返回一個future型別的物件,可以通過這個future物件判斷任務是否執行成功
execute()方法執行示意圖:
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))
// addWoker建立失敗,執行reject方法執行相應的拒絕策略
reject(command);
}
複製程式碼
如果當前執行的執行緒少於corePoolSize,則會呼叫addWorker()建立新的執行緒來執行新的任務
private boolean addWorker(Runnable firstTask, boolean core) {
retry:
for (;;) {
int c = ctl.get();
// 獲取當前執行緒池執行狀態
int rs = runStateOf(c);
// 狀態判斷,條件不符合新增執行緒失敗
if (rs >= SHUTDOWN &&
! (rs == SHUTDOWN &&
firstTask == null &&
! workQueue.isEmpty()))
return false;
for (;;) {
// 獲取執行緒池當前執行緒數
int wc = workerCountOf(c);
// 若執行緒數超過CAPACITY,返回false
// 若是新增核心執行緒,超過核心執行緒數返回false;若不是超過最大執行緒數返回false
if (wc >= CAPACITY ||
wc >= (core ? corePoolSize : maximumPoolSize))
return false;
// CAS執行緒數+1
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 {
// 再次校驗執行緒狀態是否符合新增執行緒條件
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;
}
複製程式碼
addWorker()新增執行緒時判斷了兩次執行緒狀態是否符合新增執行緒的條件
第一次判斷返回false:
①.執行緒池狀態為STOP、TIDYING或TERMINATED狀態
②.執行緒池狀態為SHUTDOWN,任務不為null即執行緒處於SHUTDOWN狀態,不允許新增任務
③.執行緒池狀態為SHUTDOWN,任務為null,但阻塞佇列為空,即新增空任務沒有意義
第二次判斷返回false:
①.執行緒池狀態為STOP、TIDYING或TERMINATED狀態
②.執行緒池狀態為SHUTDOWN且任務不為null
執行緒新增成功後,呼叫start()方法啟動執行緒,執行Worker類(繼承AQS)的run()方法
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();
// 若執行緒池處於STOP、TIDYING或TERMINATED狀態時,且執行緒沒有中斷標記,則請求中斷執行緒
// 若執行緒池處於RUNNING或SHUTDOWN狀態,且執行緒有中斷標記,再次判斷執行緒池狀態是否>=STOP,若是請求中斷執行緒
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);
}
}
複製程式碼
若當前執行緒的任務執行完,還會呼叫getTask()找阻塞佇列中是否有任務
private Runnable getTask() {
boolean timedOut = false; // Did the last poll() time out?
for (;;) {
int c = ctl.get();
// 獲取執行緒池狀態
int rs = runStateOf(c);
// 若執行緒池狀態為SHUTDOWN且阻塞佇列為空,workerCount - 1,返回null
// 若執行緒池狀態為STOP、TIDYING或TERMINATED狀態,workerCount - 1,返回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 {
// 若需要超時控制,則呼叫poll(),否則呼叫take()從阻塞佇列中獲取任務
Runnable r = timed ?
workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
workQueue.take();
if (r != null)
return r;
timedOut = true;
} catch (InterruptedException retry) {
timedOut = false;
}
}
}
複製程式碼
從getTask()原始碼可以知道執行緒池中的執行緒執行完自身任務後會一直執行阻塞佇列中的任務。當執行緒處理完阻塞佇列的任務後或者處理任務時出現異常退出迴圈,會執行processWorkerExit()方法
private void processWorkerExit(Worker w, boolean completedAbruptly) {
// completedAbruptly:true,表明執行緒執行異常,workerCount-1
// completedAbruptly:false,表明執行正常getTask()方法中已減少執行緒數量
if (completedAbruptly) // If abrupt, then workerCount wasn't adjusted
decrementWorkerCount();
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
completedTaskCount += w.completedTasks;
// 從workers移除,從執行緒池移除至多一個執行緒
workers.remove(w);
} finally {
mainLock.unlock();
}
// 嘗試終止執行緒池
tryTerminate();
int c = ctl.get();
// 若當前執行緒池狀態為RUNNING或SHUTDOWN,
if (runStateLessThan(c, STOP)) {
// 執行緒執行正常
if (!completedAbruptly) {
// 若allowCoreThreadTimeOut為true,且等待佇列有任務,至少保留一個執行緒
// 若allowCoreThreadTimeOut為false,執行緒數不少於corePoolSize
int min = allowCoreThreadTimeOut ? 0 : corePoolSize;
if (min == 0 && ! workQueue.isEmpty())
min = 1;
if (workerCountOf(c) >= min)
return; // replacement not needed
}
// 執行緒執行異常,呼叫addWorker()新增執行緒
addWorker(null, false);
}
}
複製程式碼
方法先判斷執行緒執行是否順利,若執行出現異常將執行緒數減1。然後呼叫tryTerminate()嘗試終止執行緒池。若當前執行緒池狀態為RUNNING或SHUTDOWN,視情況是否新增執行緒
tryTerminate()方法
final void tryTerminate() {
for (;;) {
int c = ctl.get();
// 若執行緒池當前狀態為RUNNING直接返回不終止
// 若狀態為TIDYING或TERMINATED,即已經準備終止
// 若狀態為SHUTDOWN且阻塞佇列非空,需要執行完任務
if (isRunning(c) ||
runStateAtLeast(c, TIDYING) ||
(runStateOf(c) == SHUTDOWN && ! workQueue.isEmpty()))
return;
// 若執行緒數不等於0,適當終止一個執行緒
if (workerCountOf(c) != 0) { // Eligible to terminate
interruptIdleWorkers(ONLY_ONE);
return;
}
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
// // 嘗試終止執行緒池
if (ctl.compareAndSet(c, ctlOf(TIDYING, 0))) {
try {
// 子類實現
terminated();
} finally {
ctl.set(ctlOf(TERMINATED, 0));
termination.signalAll();
}
return;
}
} finally {
mainLock.unlock();
}
// else retry on failed CAS
}
}
複製程式碼
submit()返回future型別的物件,通過這個future物件可以判斷任務是否執行成功,並且可以通過future的get()方法來獲取返回值,get()方法會阻塞當前執行緒直到任務完成。
public Future submit(Callable task) {
if (task == null) throw new NullPointerException();
RunnableFuture ftask = newTaskFor(task);
execute(ftask);
return ftask;
}
複製程式碼
在submit方法中呼叫newTaskFor()將Callable任務會被封裝成FutureTask物件
protected RunnableFuture newTaskFor(Callable callable) {
return new FutureTask(callable);
}
複製程式碼
FutureTask狀態:
/** Possible state transitions:
* NEW -> COMPLETING -> NORMAL
* NEW -> COMPLETING -> EXCEPTIONAL
* NEW -> CANCELLED
* NEW -> INTERRUPTING -> INTERRUPTED
*/
private volatile int state;
private static final int NEW = 0;
private static final int COMPLETING = 1;
private static final int NORMAL = 2;
private static final int EXCEPTIONAL = 3;
private static final int CANCELLED = 4;
private static final int INTERRUPTING = 5;
private static final int INTERRUPTED = 6;
複製程式碼
NEW:表示是個新的任務或者還沒被執行完的任務。這是初始狀態。
COMPLETING:任務已經執行完成或者執行任務的時候發生異常,但是任務執行結果或者異常原因還沒有儲存到outcome欄位(outcome欄位用來儲存任務執行結果,如果發生異常,則用來儲存異常原因)的時候,狀態會從NEW變更到COMPLETING。但是這個狀態會時間會比較短,屬於中間狀態。
NORMAL:任務已經執行完成並且任務執行結果已經儲存到outcome欄位,狀態會從COMPLETING轉換到NORMAL。這是一個最終態。
EXCEPTIONAL:任務執行發生異常並且異常原因已經儲存到outcome欄位中後,狀態會從COMPLETING轉換到EXCEPTIONAL。這是一個最終態。
CANCELLED:任務還沒開始執行或者已經開始執行但是還沒有執行完成的時候,使用者呼叫了cancel(false)方法取消任務且不中斷任務執行執行緒,這個時候狀態會從NEW轉化為CANCELLED狀態。這是一個最終態。
INTERRUPTING:任務還沒開始執行或者已經執行但是還沒有執行完成的時候,使用者呼叫了cancel(true)方法取消任務並且要中斷任務執行執行緒但是還沒有中斷任務執行執行緒之前,狀態會從NEW轉化為INTERRUPTING。這是一箇中間狀態。
INTERRUPTED:呼叫interrupt()中斷任務執行執行緒之後狀態會從INTERRUPTING轉換到INTERRUPTED,這是一個最終態。
所有值大於COMPLETING的狀態都表示任務已經執行完成(任務正常執行完成,任務執行異常或者任務被取消)
FutureTask.get實現
public V get() throws InterruptedException, ExecutionException {
int s = state;
if (s <= COMPLETING)
s = awaitDone(false, 0L);
return report(s);
}
複製程式碼
若狀態為NEW或者COMPLETING時呼叫awaitDone()對主執行緒進行阻塞
private int awaitDone(boolean timed, long nanos)
throws InterruptedException {
final long deadline = timed ? System.nanoTime() + nanos : 0L;
WaitNode q = null;
boolean queued = false;
for (;;) {
// 若主執行緒被中斷,拋異常
if (Thread.interrupted()) {
// 去除連結串列中超時或被中斷節點
removeWaiter(q);
throw new InterruptedException();
}
int s = state;
// 若狀態大於COMPLETING,表明任務已完成,直接返回
if (s > COMPLETING) {
if (q != null)
q.thread = null;
return s;
}
// 若狀態等於COMPLETING,讓出cpu資源
else if (s == COMPLETING) // cannot time out yet
Thread.yield();
else if (q == null)
q = new WaitNode();
else if (!queued)
// CAS設定連結串列(棧的邏輯結構)
queued = UNSAFE.compareAndSwapObject(this, waitersOffset,
q.next = waiters, q);
else if (timed) {
nanos = deadline - System.nanoTime();
// 若超時,去除連結串列中超時或被中斷節點
if (nanos <= 0L) {
removeWaiter(q);
return state;
}
// 限時祖塞
LockSupport.parkNanos(this, nanos);
}
else
// 一直阻塞
LockSupport.park(this);
}
}
複製程式碼
awaitDone()方法目的是主執行緒阻塞直至futureTask完成。若狀態為COMPLETING,表明任務完成(無論成功或失敗),但其結果被儲存在outcome欄位中,讓出cpu資源;若狀態大於COMPLETING表明任務完成且結果已存,直接返回;否則維護基於連結串列的等待棧根據是否限時阻塞執行緒節點
futureTask.run實現
public void run() {
// 若任務完成或已有其他執行此任務
if (state != NEW ||
!UNSAFE.compareAndSwapObject(this, runnerOffset,
null, Thread.currentThread()))
return;
try {
Callable c = callable;
// 若任務不為空且狀態為new
if (c != null && state == NEW) {
V result;
boolean ran;
try {
// 執行任務
result = c.call();
ran = true;
} catch (Throwable ex) {
result = null;
ran = false;
setException(ex);
}
if (ran)
set(result);
}
} finally {
// runner must be non-null until state is settled to
// 防止併發呼叫run
runner = null;
// state must be re-read after nulling runner to prevent
// leaked interrupts
int s = state;
if (s >= INTERRUPTING)
handlePossibleCancellationInterrupt(s);
}
}
複製程式碼
run()方法邏輯很簡單,執行成功set()方法儲存結果;執行異常setException()儲存異常,最後runner置空防止併發呼叫,若任務被中斷,handlePossibleCancellationInterrupt處理由於cancel(true)而取消中斷的執行緒
set,setException方法:
/**
* 任務執行成功 狀態由NEW -> COMPLETING -> NORMAL
*/
protected void set(V v) {
if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
outcome = v;
UNSAFE.putOrderedInt(this, stateOffset, NORMAL); // final state
finishCompletion();
}
}
/**
* 任務執行異常 狀態NEW -> COMPLETING -> EXCEPTIONAL
*/
protected void setException(Throwable t) {
if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
outcome = t;
UNSAFE.putOrderedInt(this, stateOffset, EXCEPTIONAL); // final state
finishCompletion();
}
}
複製程式碼
兩個方法都會finishCompletion()通知主執行緒任務已經執行完成
private void finishCompletion() {
// assert state > COMPLETING;
for (WaitNode q; (q = waiters) != null;) {
if (UNSAFE.compareAndSwapObject(this, waitersOffset, q, null)) {
for (;;) {
Thread t = q.thread;
if (t != null) {
q.thread = null;
LockSupport.unpark(t);
}
WaitNode next = q.next;
if (next == null)
break;
q.next = null; // unlink to help gc
q = next;
}
break;
}
}
done();
callable = null; // to reduce footprint
}
複製程式碼
1、執行FutureTask類的get方法時,會把主執行緒封裝成WaitNode節點並儲存在waiters連結串列中;
2、FutureTask任務執行完成後,通過UNSAFE設定waiters的值,並通過LockSupport類unpark方法喚醒主執行緒;
執行緒池關閉
執行緒池ThreadPoolExecutor提供了shutdown()和shutDownNow()用於關閉執行緒池
shutdown():按過去執行已提交任務的順序發起一個有序的關閉,其中先前提交的任務將被執行,但不會接受任何新任務
shutdownNow() :嘗試停止所有主動執行的任務,停止等待任務的處理,並返回正在等待執行的任務列表
執行緒池配置
合理地配置執行緒池,就必須首先分析任務特性,可以從以下幾個角度來分析
任務的性質:CPU密集型任務、IO密集型任務和混合型任務
任務的優先順序:高、中和低
任務的執行時間:長、中和短
任務的依賴性:是否依賴其他系統資源,如資料庫連線
性質不同的任務可以用不同規模的執行緒池分開處理。
CPU密集型任務:應配置儘可能小的執行緒,如配置Ncpu+1個執行緒的執行緒池
IO密集型任務:其執行緒並不是一直在執行任務,則應配置儘可能多的執行緒,如2*Ncpu
混合型的任務:如果可以拆分,將其拆分成一個CPU密集型任務和一個IO密集型任務,只要這兩個任務執行的時間相差不是太大,那麼分解後執行的吞吐量將高於序列執行的吞吐量。如果這兩個任務執行時間相差太大,則沒必要進行分解
可以通過Runtime.getRuntime().availableProcessors()方法獲得當前裝置的CPU個數
優先順序不同的任務可以使用優先順序佇列PriorityBlockingQueue來處理。它可以讓優先順序高的任務先執行,但優先順序低的任務可能永遠不能執行
執行時間不同的任務可以交給不同規模的執行緒池來處理,或者可以使用優先順序佇列,讓執行時間短的任務先執行
依賴資料庫連線池的任務,因為執行緒提交SQL後需要等待資料庫返回結果,等待的時間越長,則CPU空閒時間就越長,那麼執行緒數應該設定得越大,這樣才能更好地利用CPU
建議使用有界佇列,使用無界佇列的話,一旦任務積壓在阻塞佇列中的話就會佔用過多的記憶體資源,系統可能會崩潰
感謝
《java併發程式設計的藝術》
www.importnew.com/25286.html
www.jianshu.com/p/87bff5cc8…