一、爲什麼需要線程池
隨着系統用戶的逐漸增多,爲了提高用戶響應,提供一個高併發,高可用的系統。java的線程池就可以解決很多問題。很多異步,併發的場景都可以用到線程池。
1.降低重複創建線程的開銷,統一管理線程。
2.提高響應速度,任務提交後,不需要等待線程創建的過程。
3.提高線程的可管理性。重複管理線程,避免創建大量的線程增加開銷。
二、線程池的處理流程
1.線程池構造參數
private static final RejectedExecutionHandler defaultHandler = new AbortPolicy();
private final BlockingQueue<Runnable> workQueue;
private final ReentrantLock mainLock = new ReentrantLock();
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 RejectedExcutionHandler 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;
}
corePoolSize |
核心運行的poolSize,也就是當超過這個範圍的時候,就需要將新的Thread放入到等待隊列中了 |
---|---|
maximumPoolSize | 線程池最大容量大小,當大於了這個值就會將Thread由一個丟棄處理機制來處理 |
keepAliveTime | 默認都是0,當線程沒有任務處理後,保持多長時間 |
TimeUnit | 時間單位, |
ThreadFactory | 是構造Thread的方法,你可以自己去包裝和傳遞,主要實現newThread方法即可 |
BlockingQueue | 等待隊列,當達到corePoolSize的時候,就向該等待隊列放入線程信息(默認爲一個LinkedBlockingQueue),運行中的隊列屬性爲:workers,爲一個HashSet;內部被包裝了一層 |
RejectedExecutionHandler | 參數maximumPoolSize達到後丟棄處理的方法,java提供了4種丟棄處理的方法,當然你也可以自己弄,主要是要實現接口:RejectedExecutionHandler中的方法 |
2.處理流程
主要步驟:當提交一個任務到線程池時,線程池會創建一個線程來執行任務,即使其他空閒的基本線程能夠執行新任務也會創建線程,等到需要執行的任務數大於線程池基本大小時就不再創建。如果調用了線程池的prestartAllCoreThreads()方法,線程池會提前創建並啓動所有基本線程。然後將任務提交到隊列,如果隊列滿了,並且已創建的線程數小於最大線程數,則線程池會再創建新的線程執行任務。值得注意的是,如果使用了無界的任務隊列這個參數就沒什麼效果。當隊列和線程池都滿了,說明線程池處於飽和狀態,那麼必須採取一種策略處理提交的新任務。這個策略默認情況下是AbortPolicy,表示無法處理新任務時拋出異常。
三、ThreadPoolExecutor
1.具體執行方法
有一個重要的變量:
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
ctl維護兩個概念上的參數:workCount和runState。
workCount表示有效的線程數量,
runState表示線程池的運行狀態。
public void execute(Runnable command) {
//檢查command不能爲null
if (command == null)
throw new NullPointerException();
/*
* 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.
*/
int c = ctl.get();
//如果當前線程小於corePoolSize
if (workerCountOf(c) < corePoolSize) {
//如果添加Worker線程成功,則返回
if (addWorker(command, true))
return;
c = ctl.get();
}
//如果當前正在運行階段並且可以將線程入隊
if (isRunning(c) && workQueue.offer(command)) {
//再次檢查ctl狀態
int recheck = ctl.get();
//如果不在運行狀態了,那麼就從隊列中移除任務
if (! isRunning(recheck) && remove(command))
reject(command);
//如果在運行階段,但是Worker數量爲0,調用addWorker方法
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
//如果不能入隊,且不能創建Worker,那麼reject
else if (!addWorker(command, false))
reject(command);
}
以上是核心的執行任務的方法:
從註釋中可以看到execute方法分爲三步:
(1)如果小於corePoolSize的線程正在運行,那麼創建一個核心線程,並將任務作爲核心線程的第一個任務
(2)如果一個任務可以加入到隊列中,然後需要在此檢查是否需要新建一個線程(因爲可能存在一個線程在上次檢查完之後被回收了)或者因爲線程池停止了。所以需要再次檢查狀態,如果不在RUNNING狀態並且能夠成功移除任務的話,那麼調用reject方法,否則就調用addWorker(null,false)方法。
(3)如果任務不能放入隊列,會首先嚐試添加一個新線程(非核心線程)。如果失敗,則調用reject方法。