說真的,我一直認爲線程池很簡單,也沒去看過它的實現,大概瞭解過其中的原理,但是並未深入學習。一方面,瞭解過之後很長時間不去看,非常容易忘;另一方面,還是深入源碼得到的信息纔會比較深刻,還能避免背書式學習。
繼承結構說明
在Executors
中,有幾個靜態方法,預設了幾個ThreadPoolExecutor
,其實主要的區別在於new一個實例的時候,傳入的參數不一樣。關於這幾個預設的ThreadPoolExecutor
可以在瞭解其參數詳細的定義後,再進行分析。
用法簡介 - 註釋
ThreadPoolExecutor
的類前註釋,當閱讀理解隨便看看,看個大概,都比網上的文章要優質。
An
ExecutorService
that executes each submitted task using one of possibly several pooled threads, normally configured using Executors factory methods.Thread pools address two different problems: they usually provide improved performance when executing large numbers of asynchronous tasks, due to reduced per-task invocation overhead, and they provide a means of bounding and managing the resources, including threads, consumed when executing a collection of tasks. Each
ThreadPoolExecutor
also maintains some basic statistics, such as the number of completed tasks.To be useful across a wide range of contexts, this class provides many adjustable parameters and extensibility hooks. However, programmers are urged to use the more convenient Executors factory methods
Executors.newCachedThreadPool
(unbounded thread pool, with automatic thread reclamation),Executors.newFixedThreadPool
(fixed size thread pool) andExecutors.newSingleThreadExecutor
(single background thread), that preconfigure settings for the most common usage scenarios. Otherwise, use the following guide when manually configuring and tuning this class:Core and maximum pool sizes
A
ThreadPoolExecutor
will automatically adjust the pool size (seegetPoolSize
) according to the bounds set bycorePoolSize
(seegetCorePoolSize
) andmaximumPoolSize
(seegetMaximumPoolSize
). When a new task is submitted in methodexecute(Runnable)
, if fewer thancorePoolSize
threads are running, a new thread is created to handle the request, even if other worker threads are idle. Else if fewer thanmaximumPoolSize
threads are running, a new thread will be created to handle the request only if the queue is full. By settingcorePoolSize
andmaximumPoolSize
the same, you create a fixed-size thread pool. By settingmaximumPoolSize
to an essentially unbounded value such asInteger.MAX_VALUE
, you allow the pool to accommodate an arbitrary number of concurrent tasks. Most typically, core and maximum pool sizes are set only upon construction, but they may also be changed dynamically usingsetCorePoolSize
andsetMaximumPoolSize
.當一個新任務通過execute()方法執行時,如果當前運行的線程數小於核心數,那麼會新開一個線程處理這個任務,即使別的線程在閒置;如果當前在跑的線程數大於等於核心數但小於最大線程數,若Queue沒有滿,那麼會將改任務加入Queue中,若Queue滿了,那麼會新增一個線程去處理改任務;如果大於等於最大線程數,會觸發決絕策略。
On-demand construction
By default, even core threads are initially created and started only when new tasks arrive, but this can be overridden dynamically using method
prestartCoreThread
orprestartAllCoreThreads
. You probably want to prestart threads if you construct the pool with a non-empty queue.可以預先初始化好線程數,不用一次次execute時慢慢增加。
Creating new threads
New threads are created using a
ThreadFactory
. If not otherwise specified, aExecutors.defaultThreadFactory
is used, that creates threads to all be in the sameThreadGroup
and with the sameNORM_PRIORITY
priority and non-daemon status. By supplying a differentThreadFactory
, you can alter the thread’s name, thread group, priority, daemon status, etc. If aThreadFactory
fails to create a thread when asked by returning null fromnewThread
, the executor will continue, but might not be able to execute any tasks. Threads should possess the “modifyThread” RuntimePermission. If worker threads or other threads using the pool do not possess this permission, service may be degraded: configuration changes may not take effect in a timely manner, and a shutdown pool may remain in a state in which termination is possible but not completed.創建新線程是通過
ThreadFactory
的newThread()
方法,默認實現是Executors.defaultThreadFactory
。可自定義線程的名稱、是否爲守護進程、優先級等。Keep-alive times
If the pool currently has more than
corePoolSize
threads, excess threads will be terminated if they have been idle for more than thekeepAliveTime
(seegetKeepAliveTime(TimeUnit)
). This provides a means of reducing resource consumption when the pool is not being actively used. If the pool becomes more active later, new threads will be constructed. This parameter can also be changed dynamically using methodsetKeepAliveTime(long, TimeUnit)
. Using a value ofLong.MAX_VALUE TimeUnit.NANOSECONDS
effectively disables idle threads from ever terminating prior to shut down. By default, the keep-alive policy applies only when there are more thancorePoolSize
threads, but methodallowCoreThreadTimeOut(boolean)
can be used to apply this time-out policy to core threads as well, so long as thekeepAliveTime
value is non-zero.當線程數超過核心數,如果線程閒置超過
keepAliveTime
,那麼該線程將會被關閉。也可以通過allowCoreThreadTimeOut(boolean)
,回收核心線程。可拓展啊,牛逼。Queuing
Any
BlockingQueue
may be used to transfer and hold submitted tasks. The use of this queue interacts with pool sizing:
- If fewer than
corePoolSize
threads are running, theExecutor
always prefers adding a new thread rather than queuing.- If
corePoolSize
or more threads are running, theExecutor
always prefers queuing a request rather than adding a new thread.- If a request cannot be queued, a new thread is created unless this would exceed
maximumPoolSize
, in which case, the task will be rejected.大於核心數,存儲到queue;如果queue滿了,新建線程,直到到達
maximumPoolSize
。There are three general strategies for queuing:
- Direct handoffs. A good default choice for a work queue is a
SynchronousQueue
that hands off tasks to threads without otherwise holding them. Here, an attempt to queue a task will fail if no threads are immediately available to run it, so a new thread will be constructed. This policy avoids lockups when handling sets of requests that might have internal dependencies. Direct handoffs generally require unboundedmaximumPoolSizes
to avoid rejection of new submitted tasks. This in turn admits the possibility of unbounded thread growth when commands continue to arrive on average faster than they can be processed.- Unbounded queues. Using an unbounded queue (for example a
LinkedBlockingQueue
without a predefined capacity) will cause new tasks to wait in the queue when allcorePoolSize
threads are busy. Thus, no more thancorePoolSize
threads will ever be created. (And the value of themaximumPoolSize
therefore doesn’t have any effect.) This may be appropriate when each task is completely independent of others, so tasks cannot affect each others execution; for example, in a web page server. While this style of queuing can be useful in smoothing out transient bursts of requests, it admits the possibility of unbounded work queue growth when commands continue to arrive on average faster than they can be processed.- Bounded queues. A bounded queue (for example, an
ArrayBlockingQueue
) helps prevent resource exhaustion when used with finitemaximumPoolSizes
, but can be more difficult to tune and control. Queue sizes and maximum pool sizes may be traded off for each other: Using large queues and small pools minimizes CPU usage, OS resources, and context-switching overhead, but can lead to artificially low throughput. If tasks frequently block (for example if they are I/O bound), a system may be able to schedule time for more threads than you otherwise allow. Use of small queues generally requires larger pool sizes, which keeps CPUs busier but may encounter unacceptable scheduling overhead, which also decreases throughput.隊列的三種策略。第一種,不讓存儲到到queue,一有存儲動作,就新建線程,最大線程數設置成無限制;第二種,隊列的長度無限,這樣會導致最大線程數失效;第三種,有界隊列。
Rejected tasks
New tasks submitted in method execute(Runnable) will be rejected when the Executor has been shut down, and also when the Executor uses finite bounds for both maximum threads and work queue capacity, and is saturated. In either case, the execute method invokes the
RejectedExecutionHandler.rejectedExecution(Runnable, ThreadPoolExecutor)
method of itsRejectedExecutionHandler
. Four predefined handler policies are provided:
- In the default
ThreadPoolExecutor.AbortPolicy
, the handler throws a runtimeRejectedExecutionException
upon rejection.- In
ThreadPoolExecutor.CallerRunsPolicy
, the thread that invokes execute itself runs the task. This provides a simple feedback control mechanism that will slow down the rate that new tasks are submitted.- In
ThreadPoolExecutor.DiscardPolicy
, a task that cannot be executed is simply dropped.- In
ThreadPoolExecutor.DiscardOldestPolicy
, if the executor is not shut down, the task at the head of the work queue is dropped, and then execution is retried (which can fail again, causing this to be repeated.)It is possible to define and use other kinds of
RejectedExecutionHandler
classes. Doing so requires some care especially when policies are designed to work only under particular capacity or queuing policies.拒絕任務發生在線程池已經處於SHUT_DOWN 狀態,或最大線程數以及隊列長度有限,並處於飽和狀態時。預置的拒絕策略有4種,分別爲:第一種,直接拋異常;第二種,調用者在自己的線程中執行該任務;第三種,直接拋棄;第四種,丟棄等待時間最久的那個任務。
自己可以自定義拒絕策略。
Hook methods
This class provides protected overridable
beforeExecute(Thread, Runnable)
andafterExecute(Runnable, Throwable)
methods that are called before and after execution of each task. These can be used to manipulate the execution environment; for example, reinitializing ThreadLocals, gathering statistics, or adding log entries. Additionally, method terminated can be overridden to perform any special processing that needs to be done once the Executor has fully terminated.
If hook, callback, or BlockingQueue methods throw exceptions, internal worker threads may in turn fail, abruptly terminate, and possibly be replaced.有鉤子函數可以做到AOP的效果,分別在執行前,執行後。具體用途可以根據自己需求來進行自定義。
Queue maintenance
Method
getQueue()
allows access to the work queue for purposes of monitoring and debugging. Use of this method for any other purpose is strongly discouraged. Two supplied methods,remove(Runnable)
andpurge
are available to assist in storage reclamation when large numbers of queued tasks become cancelled.可獲取
Reclamation
A pool that is no longer referenced in a program AND has no remaining threads may be reclaimed (garbage collected) without being explicitly shutdown. You can configure a pool to allow all unused threads to eventually die by setting appropriate keep-alive times, using a lower bound of zero core threads and/or setting
allowCoreThreadTimeOut(boolean)
.可以通過設置讓線程被回收掉。
Extension example
Most extensions of this class override one or more of the protected hook methods. For example, here is a subclass that adds a simple pause/resume feature:
class PausableThreadPoolExecutor extends ThreadPoolExecutor { private boolean isPaused; private ReentrantLock pauseLock = new ReentrantLock(); private Condition unpaused = pauseLock.newCondition(); public PausableThreadPoolExecutor(...) { super(...); } protected void beforeExecute(Thread t, Runnable r) { super.beforeExecute(t, r); pauseLock.lock(); try { while (isPaused) unpaused.await(); } catch (InterruptedException ie) { t.interrupt(); } finally { pauseLock.unlock(); } } public void pause() { pauseLock.lock(); try { isPaused = true; } finally { pauseLock.unlock(); } } public void resume() { pauseLock.lock(); try { isPaused = false; unpaused.signalAll(); } finally { pauseLock.unlock(); } } }
線程池的狀態
狀態 | 說明 |
---|---|
RUNNING | Accept new tasks and process queued tasks |
SHUTDOWN | Don’t accept new tasks, but process queued tasks |
STOP | Don’t accept new tasks, don’t process queued tasks, and interrupt in-progress tasks |
TIDYING | All tasks have terminated, workerCount is zero, the thread transitioning to state TIDYING will run the terminated() hook method |
TERMINATED | terminated() has completed |
狀態變換:
- RUNNING -> SHUTDOWN
On invocation of shutdown() - (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
關鍵屬性
名稱 | 含義 |
---|---|
corePoolSize | 核心線程池大小 |
maximumPoolSize | 最大線程池大小 |
keepAliveTime | 線程最大空閒時間 |
workQueue | 線程等待隊列 |
threadFactory | 線程創建工廠 |
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;
}
項目中用到的線程池,在MDC中加入了追蹤每一個請求的traceId,通過log輸出:
public class SysThreadPool {
private ThreadPoolExecutor threadPoolExecutor;
private SysThreadPool(ThreadPoolExecutor threadPoolExecutor) {
this.threadPoolExecutor = threadPoolExecutor;
}
public static void doExecute(Runnable task) {
Map<String, String> context = MDC.getCopyOfContextMap();
getInstance().threadPoolExecutor.execute(() -> {
// 將父線程的MDC內容傳給子線程
if(context != null){
MDC.setContextMap(context);
}
try {
task.run();
} catch (Exception e){
e.printStackTrace();
}finally {
// 清空MDC內容
MDC.clear();
}
});
}
private static SysThreadPool getInstance() {
return InstanceHolder.threadPool;
}
// 單例模式的容器實現方式
private static class InstanceHolder {
private static final Integer CORE_POOL_SIZE = 50;
private static final Integer MAX_POOL_SIZE = 500;
private static final Long KEEP_ALIVE_TIME = 2L;
private static final TimeUnit TIME_UNIT = TimeUnit.MINUTES;
private static final LinkedTransferQueue QUEUE = new LinkedTransferQueue();
private static final ThreadFactory FACTORY = new ThreadFactory() {
private final AtomicInteger integer = new AtomicInteger();
@Override
public Thread newThread(Runnable r) {
return new Thread(r, "SysThreadPool thread: " + integer.getAndIncrement());
}
};
public static final SysThreadPool threadPool = new SysThreadPool(
new ThreadPoolExecutor(CORE_POOL_SIZE, MAX_POOL_SIZE, KEEP_ALIVE_TIME, TIME_UNIT, QUEUE, FACTORY));
}
}
貌似有點問題,MAX_POOL_SIZE
應該沒有生效,因爲隊列是無界隊列。
JDK預置線程池
打開Executors.java
,裏面很多static方法,且它的構造方法時私有的,所以這個類的用處也很明顯,只是作爲一個Facade。
/** Cannot instantiate. */
private Executors() {}
- newCachedThreadPool。這種Queue的策略,對應前面所描述的第一種,Direct Handsoff。
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
核心數爲0,最大線程數相當於無界,而SynchronousQueue則是一個空隊列,不能存放任務,所以會直接創建線程,直到達到最大的線程數。所有閒置60s之後的線程會被回收,因此執行完之後不會佔用任何資源。
- newFixedThreadPool。這種Queue策略,對應前面所描述的第二種,Unbounded queues。
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
核心數等於最大線程數,因爲LinkedBlockingQueue
無界,所以最大線程數無實際意義。所以這種線程池保證最大線程數爲nThreads,且由於keepAliveTime爲0,所以不對線程進行回收;後來的任務全部扔進queue裏面,等待空閒的線程來執行。
- newSingleThreadExecutor。這種Queue策略,對應前面描述的第二種,Unbounded queues。
public static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory) {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(),
threadFactory));
}
在FixThreadPool的基礎上,保證線程數最大爲1。
線程池如何工作
一般通過線程池的execute方法執行任務,在execute方法中,變濃縮了上面 內容的處理邏輯。
/*
* Proceed in 3 steps:
*
* 1. If fewer than corePoolSize threads are running, try to
* start a new thread with the given command as its first
* task. The call to addWorker atomically checks runState and
* workerCount, and so prevents false alarms that would add
* threads when it shouldn't, by returning false.
*
* 2. If a task can be successfully queued, then we still need
* to double-check whether we should have added a thread
* (because existing ones died since last checking) or that
* the pool shut down since entry into this method. So we
* recheck state and if necessary roll back the enqueuing if
* stopped, or start a new thread if there are none.
*
* 3. If we cannot queue task, then we try to add a new
* thread. If it fails, we know we are shut down or saturated
* and so reject the task.
*/
主要分三步:①與corePoolSize比較大小,小就新建線程運行任務。②大於corePoolSize,放入隊列。③放入隊列失敗,再次嘗試新建線程執行任務,上限是maximumPoolSize。④創建新線程失敗,那麼調用拒絕策略。
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
int c = ctl.get();
// ①與corePoolSize比較大小,小就新建線程運行任務。
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true))
return;
c = ctl.get();
}
// ②大於corePoolSize,放入隊列。
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);
}
// ③放入隊列失敗,再次嘗試新建線程執行任務,上限是maximumPoolSize。
else if (!addWorker(command, false))
// ④創建新線程失敗,那麼調用拒絕策略。
reject(command);
}
其中比較關鍵的一個步驟可能是addWorker這個方法,在這其中,會調用threadFactory新建線程,並調用Thread的start()方法來開啓線程。
其中有一段標號與break、continue的使用,之前都沒見過、沒用過。
①可以給語句塊加標號賦予它們名稱,標號位於語句之前,且語句前只允許加一個標號,標號後面不能跟大括號,且後面只能跟for.while.do-while等循環。
②標號只能被continue和break引用。通過用標號,我們可以對外層循環進行控制。
private boolean addWorker(Runnable firstTask, boolean core) {
retry:
for (int c = ctl.get();;) {
// Check if queue empty only if necessary.
if (runStateAtLeast(c, SHUTDOWN)
&& (runStateAtLeast(c, STOP)
|| firstTask != null
|| workQueue.isEmpty()))
return false;
for (;;) {
if (workerCountOf(c)
>= ((core ? corePoolSize : maximumPoolSize) & COUNT_MASK))
return false;
// 通過CAS更新工作線程數量
if (compareAndIncrementWorkerCount(c))
// break外層循環
break retry;
c = ctl.get(); // Re-read ctl
if (runStateAtLeast(c, SHUTDOWN))
// continue外層循環
continue retry;
// else CAS failed due to workerCount change; retry inner loop
}
}
boolean workerStarted = false;
boolean workerAdded = false;
Worker w = null;
try {
// 調用threadFactory新建線程
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 c = ctl.get();
if (isRunning(c) ||
(runStateLessThan(c, STOP) && 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(Runnable firstTask) {
setState(-1); // inhibit interrupts until runWorker
this.firstTask = firstTask;
this.thread = getThreadFactory().newThread(this);
}
reject即直接調用rejectedExecution方法。
final void reject(Runnable command) {
handler.rejectedExecution(command, this);
}
線程池如何拉取隊列中的任務並執行?
這裏有一個細節,需要注意,Worker這個私有內部類,它實現了Runnable,並且通過threadFactory創建線程的時候,將自己作爲Runnable,傳給了創建的線程,而同時傳進去的task被專門用firstTask保存起來。
Worker(Runnable firstTask) {
setState(-1); // inhibit interrupts until runWorker
this.firstTask = firstTask;
this.thread = getThreadFactory().newThread(this);
}
所以,上面的t.start()
:
if (workerAdded) {
// 啓動線程
t.start();
workerStarted = true;
}
實際上是執行了Worker的run方法,也就是:
public void run() {
runWorker(this);
}
final void runWorker(Worker w) {
Thread wt = Thread.currentThread();
// 第一次執行時,會首先執行之前傳入的task。
Runnable task = w.firstTask;
w.firstTask = null;
w.unlock(); // allow interrupts
boolean completedAbruptly = true;
try {
// 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 {
beforeExecute(wt, task);
try {
task.run();
afterExecute(task, null);
} catch (Throwable ex) {
afterExecute(task, ex);
throw ex;
}
} finally {
task = null;
w.completedTasks++;
w.unlock();
}
}
completedAbruptly = false;
} finally {
processWorkerExit(w, completedAbruptly);
}
}
其中還包含beforeExecute、afterExecute函數的執行。
Reference:
https://www.jianshu.com/p/f030aa5d7a28
https://www.cnblogs.com/dolphin0520/p/3932921.html