Java虛擬機啓動一個新線程的成本比較高,當程序中需要啓動大量且生存期很短暫的線程時,可以考慮使用線程池。Java爲我們提供了四種線程池使用。
1)創建單個線程的線程池
ExecutorService threadPool = Executors.newSingleThreadExecutor()
2)創建多個線程的線程池
ExecutorService threadPool = Executors.newFixdThreadPool(7)
3)創建可變線程數的線程池
ExecutorService threadPool = Executors.newCachedThreadPool()
4)創建可調度的線程池
ScheduledExecutorService threadPool =Executors.newScheduledThreadPool()
可以看到,四種線程池均是使用Exectors類創建,進入該類查看,創建各種類型的線程池最終是構造了ThreadPoolExecutor對象。只不過不同的線程池構造ThreadPoolExecutor參數不同。如下
1)newSingleThreadExecutor:
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
2)newFixedThreadPool:
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
3)newCachedThreadPool
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
4)newScheduledThreadPool
ScheduledThreadPoolExecutor是繼承了ThreadPoolExecutor
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
}
這裏我們簡單回憶下線程池的使用方法,外部通過調用sumbit(Runnable runnable)將runnable傳遞給線程池。如下:
public class ThreadPoolTest {
public static void main(String[] args) {
//創建一個具有固定線程數的線程池
ExecutorService pool = Executors.newFixedThreadPool(6);
Runn r1 = new Runn();
Runn r2 = new Runn();
pool.submit(r1);
pool.submit(r2);
pool.shutdown();
}
}
class Runn implements Runnable{
public void run() {
for(int i=0;i<30;i++){
System.out.println(Thread.currentThread().getName()+" "+i);
}
}
}
ThreadPoolExecutor.sumbit()方法如下:
public <T> Future<T> submit(Callable<T> task) {
if (task == null) throw new NullPointerException();
RunnableFuture<T> ftask = newTaskFor(task);
execute(ftask);
return ftask;
}
將外部傳遞過來的Runnable進行封裝,調用execute()方法。那麼我們就將該方法作爲起點,分析線程池執行過程及原理。先上一張流程圖:
當調用execute(),線程池首先會判斷當前正在執行任務的線程數量是否已經超過了核心線程數,如果沒超過,那麼啓動一個新線程並將execute()傳入的runnable作爲first task執行;若當前線程數已達到核心線程數,則將執行任務放入隊列中等待執行。(線程池中正在跑着的線程,會首先執行first task,執行完後後不斷從隊列中取出執行任務執行,那這也達到了線程複用的目的。);若當前隊列已滿,則接着判斷當前線程數量,是否已達到了線程池的最大線程數。若沒達到。則創建普通線程執行。注意,這裏所謂的核心線程和普通線程只是在創建時傳入的一個布爾值。當然普通線程在執行完自己的first task也會不斷從隊列中讀取任務執行。若當前線程數已達到最大線程數,則執行拒絕策略。下面我們看代碼:
execute()
/**
* 將該Runnable任務加入線程池並在未來某個時刻執行
* 該任務可能執行在一個新的線程 或 一個已存在的線程池中的線程
* 如果該任務提交失敗,可能是因爲線程池已關閉,或者已達到線程池隊列和線程數已滿.
* 該Runnable將交給RejectedExecutionHandler處理,拋出RejectedExecutionException
*/
public void execute(Runnable command) {
if (command == null){
//如果沒傳入Runnable任務,則拋出空指針異常
throw new NullPointerException();
}
int c = ctl.get();
//當前線程數 小於 核心線程數
if (workerCountOf(c) < corePoolSize) {
//直接開啓新的線程,並將Runnable傳入作爲第一個要執行的任務,成功返回true,否則返回false
//第二個參數true標明是核心線程
if (addWorker(command, true)){
return;
}
c = ctl.get();
}
//c < SHUTDOWN代表線程池處於RUNNING狀態 + 將Runnable添加到任務隊列,如果添加成功返回true失敗返回false
if (isRunning(c) && workQueue.offer(command)) {
int recheck = ctl.get();
//成功加入隊列後,再次檢查是否需要添加新線程(因爲已存在的線程可能在上次檢查後銷燬了,或者線程池在進入本方法後關閉了)
if (! isRunning(recheck) && remove(command)){
//如果線程池處於非RUNNING狀態 並且 將該Runnable從任務隊列中移除成功,則拒絕執行此任務
//交給RejectedExecutionHandler調用rejectedExecution方法,拒絕執行此任務
reject(command);
}else if (workerCountOf(recheck) == 0){
//如果線程池線程數量爲0,則創建一條普通線程,去執行
//這裏第二次參數false標明是普通線程
addWorker(null, false);
}
}else if (!addWorker(command, false))
//如果線程池處於非RUNNING狀態 或 將Runnable添加到隊列失敗(隊列已滿導致),則執行默認的拒絕策略
reject(command);
}
我們可以看到,線程池把線程和所要執行的first runnable封裝成了一個worker類,這裏先簡單的看一下,知道worker封裝了thread和Runnable即可,細節我們後邊講解,如下:
Worker類:
private final class Worker extends AbstractQueuedSynchronizer
implements Runnable
{
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);
}
..................
接下來,我們就看核心方法addWorker()
/**
* 往線程池中添加Worker對象
* @param firstTask 線程中第一個要執行的任務
* @param core 是否爲核心線程
* @return 添加是否成功
*/
private boolean addWorker(Runnable firstTask, boolean core) {
//這裏有兩層[死循環],外循環:不停的判斷線程池的狀態
retry: for (;;) {
int c = ctl.get();
int rs = runStateOf(c);
//一系列判斷條件:線程池關閉,Runnable爲空,隊列爲空,則直接return false,代表Runnable添加失敗
if (rs >= SHUTDOWN && ! (rs == SHUTDOWN && firstTask == null && !workQueue.isEmpty())){
return false;
}
//內循環:不停的檢查線程容量
for (;;) {
int wc = workerCountOf(c);
//超過線程數限制,則return false
if (wc >= CAPACITY || wc >= (core ? corePoolSize : maximumPoolSize)){
return false;
}
//添加線程成功,則直接跳出兩層循環,繼續往下執行.
//注意:這裏只是把線程數成功添加到了AtomicInteger記錄的線程池數量中,真正的Runnable添加,在下面的代碼中進行
if (compareAndIncrementWorkerCount(c)){
break retry;
}
//再次判斷線程池最新狀態,如果狀態改變了(內循環和外循環記錄的狀態不符),則重新開始外層死循環
c = ctl.get(); // Re-read ctl
if (runStateOf(c) != rs){
continue retry;
}
}
}
//結束循環之後,開始真正的創建線程.
boolean workerStarted = false;
boolean workerAdded = false;
Worker w = null;
try {
//創建一個Worker對象,並將Runnable當做參數傳入
w = new Worker(firstTask);
//從worker對象中取出線程
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)) {
//檢查準備執行Runnable的Thread的狀態,如果該Thread已處於啓動狀態,則拋出狀態異常(因爲目前還沒啓動呢)
if (t.isAlive()){
throw new IllegalThreadStateException();
}
//將新創建的worker,添加到worker集合
workers.add(w);
...
workerAdded = true;
}
} finally {
//釋放鎖
mainLock.unlock();
}
if (workerAdded) {
//★Thread開始啓動
t.start();
workerStarted = true;
}
}
} finally {
//添加worker失敗
if (! workerStarted){
addWorkerFailed(w);
}
}
return workerStarted;
}
可以看到,如果添加worker成功,最終會調用worker.thread.start()來開啓線程執行任務,那我們繼續進入worker類,看看裏面封裝的thread執行的是什麼Runnable
看看他的構造方法:
Worker(Runnable firstTask) {
setState(-1); // inhibit interrupts until runWorker
this.firstTask = firstTask;
this.thread = getThreadFactory().newThread(this);
}
從線程工廠中獲取線程,並將worker自己作爲runnable傳入線程,worker實現了runnable接口。也就是說addWorker()方法中調用worker.thread.start()方法最終會調用到worker複寫的run()方法,worker.run()方法如下:
public void run() {
runWorker(this);
}
接着往裏看runWorker(this)
final void runWorker(Worker w) {
Thread wt = Thread.currentThread();
//取出Worker對象中的Runnable任務
Runnable task = w.firstTask;
boolean completedAbruptly = true;
...
try {
//注意這個while循環,在這裏實現了 [線程複用]
//首先執行first task.run(),然後不斷通過getTask()從隊列裏取任務
//直到隊列中無任務,循環結束
while (task != null || (task = getTask()) != null) {
//上鎖
w.lock();
//檢查Thread狀態的代碼
...
try {
...
try {
//執行Worker中的Runnable任務
task.run();
} catch (...) {
...catch各種異常
}
} finally {
//置空任務(這樣下次循環開始時,task依然爲null,需要再通過getTask()取) + 記錄該Worker完成任務數量 + 解鎖
task = null;
w.completedTasks++;
w.unlock();
}
}
//該線程已經從隊列中取不到任務了,改變標記
completedAbruptly = false;
} finally {
//線程移除
processWorkerExit(w, completedAbruptly);
}
}
也就是說只要隊列中有任務,線程就會不斷從其中取,直到隊列中沒有任務了。但是while循環不會結束。爲什麼呢?因爲getTask()方法中是for死循環。所以被創建出來的線程會一直運行着,實現複用。該線程run()方法結束,該線程也就銷燬了,隨後調用processWokerExit()移除線程(更新降低線程數量)
分析到這裏,我們就大概理解了線程池的工作原理,但一些細節,例如getTask()如何取任務?processWorkerExit()如何移除任務?拒絕策略又如何?這些細節,大家可以移步參考文章:點擊打開鏈接 繼續學習。