什么是線程池:
為了避免系統頻繁的創建和銷毀線程,我們可以將創建的線程進行復用。在線程池中總有那么幾個活躍的線程,也有一定的最大值限制,一個業務使用完線程之后,不是立即銷毀而是將其放入到線程池中,從而實現線程的復用。簡而言之:創建線程變成了從線程池獲取空閑的線程,關閉線程變成了向池子中歸還線程。
使用線程池的好處
Java中的線程池是運用場景最多的並發框架,幾乎所有需要異步或並發執行任務的程序都可以使用線程池。在開發過程中,合理地使用線程池能夠帶來3個好處:
第一:降低資源消耗。通過重復利用已創建的線程降低在頻繁創建和銷毀線程上所帶來的性能損耗。
第二:提高響應速度。當任務到達時,任務可以不需要等到線程創建就能立即執行。
第三:提高線程的可管理性。線程是稀缺資源,如果無限制地創建,不僅會消耗系統資源,還會降低系統的穩定性,使用線程池可以進行統一分配、調優和監控。但是,要做到合理利用線程池,必須對其實現原理了如指掌。
線程池的處理流程
當提交一個新任務到線程池時,線程池的處理流程如下
- 線程池判斷核心線程池里是的線程是否都在執行任務,如果不是,則創建一個新的工作線程來執行任務。如果核心線程池里的線程都在執行任務,則進入下一個流程
- 線程池判斷工作隊列是否已滿。如果工作隊列沒有滿,則將新提交的任務儲存在這個工作隊列里。如果工作隊列滿了,則進入下一個流程。
- 線程池判斷其內部線程是否都處於工作狀態。如果沒有,則創建一個新的工作線程來執行任務。如果已滿了,則交給飽和策略來處理這個任務。
線程池在執行excute()方法時,主要有以下四種情況:
1、如果當前運行的線程少於corePoolSize,則創建新線程來執行任務(需要獲得全局鎖);
2、如果運行的線程等於或多於corePoolSize ,則將任務加入BlockingQueue;
3、如果無法將任務加入BlockingQueue(隊列已滿並且正在運行的線程數量小於 maximumPoolSize),則創建新的線程來處理任務(需要獲得全局鎖)
4、如果創建新線程將使當前運行的線程超出maxiumPoolSize(隊列已滿並且正在運行的線程數量大於或等於 maximumPoolSize),任務將被拒絕,並調用RejectedExecutionHandler.rejectedExecution()方法(線程池會拋出異常,告訴調用者"我不能再接受任務了");
線程池采取上述的流程進行設計是為了減少獲取全局鎖的次數。在線程池完成預熱(當前運行的線程數大於或等於corePoolSize)之后,幾乎所有的excute方法調用都執行步驟2;
5、當一個線程完成任務時,它會從隊列中取下一個任務來執行;
6、當一個線程無事可做,超過一定的時間(keepAliveTime)時,線程池會判斷,如果當前運行的線程數大於 corePoolSize,那么這個線程就被停掉。所以線程池的所有任務完成后,它最終會收縮到 corePoolSize 的大小
任務的管理是一件比較容易的事,復雜的是線程的管理,這會涉及線程數量、等待/喚醒、同步/鎖、線程創建和死亡等問題。ThreadPoolExecutor與線程相關的幾個成員變量是:keepAliveTime、allowCoreThreadTimeOut、poolSize、corePoolSize、maximumPoolSize,它們共同負責線程的創建和銷毀。
corePoolSize:
核心線程池大小,即在沒有任務需要執行的時候線程池的大小,並且只有在工作隊列滿了的情況下才會創建超出這個數量的線程。這里需要注意的是:在剛剛創建ThreadPoolExecutor的時候,線程並不會立即創建,而是要等到有任務提交時才會創建,除非調用了prestartCoreThread/prestartAllCoreThreads事先創建核心線程。再考慮到keepAliveTime和allowCoreThreadTimeOut超時參數(executor.allowCoreThreadTimeOut(true))的影響,所以沒有任務需要執行的時候,線程池的大小不一定是corePoolSize。
maximumPoolSize:
線程池中允許創建的最大線程數,線程池中的當前線程數目不會超過該值。如果隊列中任務已滿,並且當前線程個數(poolSize)小於maximumPoolSize,那么會創建新的線程來執行任務。這里值得一提的是largestPoolSize,該變量記錄了線程池在整個生命周期中曾經出現的最大線程個數。為什么說是曾經呢?因為線程池創建之后,可以調用setMaximumPoolSize()改變運行的最大線程的數目。
poolSize:
線程池中當前線程的數量,當該值為0的時候,意味着沒有任何線程,線程池會終止;同一時刻,poolSize不會超過maximumPoolSize。
現在我們通過ThreadPoolExecutor.execute()方法,看一下這3個屬性的關系,以及線程池如何處理新提交的任務。以下源碼基於JDK1.6.0_37版本。
public void execute(Runnable command) { if (command == null) throw new NullPointerException(); if (poolSize >= corePoolSize || !addIfUnderCorePoolSize(command)) { if (runState == RUNNING && workQueue.offer(command)) { if (runState != RUNNING || poolSize == 0) ensureQueuedTaskHandled(command); } else if (!addIfUnderMaximumPoolSize(command)) reject(command); // is shutdown or saturated
} } private boolean addIfUnderCorePoolSize(Runnable firstTask) { Thread t = null; final ReentrantLock mainLock = this.mainLock; mainLock.lock(); try { if (poolSize < corePoolSize && runState == RUNNING) t = addThread(firstTask); } finally { mainLock.unlock(); } if (t == null) return false; t.start(); return true; } private boolean addIfUnderMaximumPoolSize(Runnable firstTask) { Thread t = null; final ReentrantLock mainLock = this.mainLock; mainLock.lock(); try { if (poolSize < maximumPoolSize && runState == RUNNING) t = addThread(firstTask); } finally { mainLock.unlock(); } if (t == null) return false; t.start(); return true; }
新提交一個任務時的處理流程很明顯:
1、如果線程池的當前大小還沒有達到基本大小(poolSize < corePoolSize),那么就新增加一個線程處理新提交的任務;
2、如果當前大小已經達到了基本大小,就將新提交的任務提交到阻塞隊列排隊,等候處理.workQueue.offer(command);
3、如果工作隊列已滿,並且當前大小poolSize沒有達到maximumPoolSize,那么就新增線程來處理任務;
4、如果隊列已滿,並且當前線程數目也已經達到上限(maximumPoolSize),那么意味着線程池的處理能力已經達到了極限,此時需要拒絕新增加的任務。至於如何拒絕處理新增
的任務,取決於線程池的拒絕策略RejectedExecutionHandler。
接下來我們看下allowCoreThreadTimeOut和keepAliveTime屬性的含義。在壓力很大的情況下,線程池中的所有線程都在處理新提交的任務或者是在排隊的任務,這個時候線程池處在忙碌狀態。如果壓力很小,那么可能很多線程都處在空閑狀態,這個時候為了節省系統資源,回收這些沒有用的空閑線程,就必須提供一些超時機制,這也是線程池大小調節策略的一部分。通過corePoolSize和maximumPoolSize,控制如何新增線程;通過allowCoreThreadTimeOut和keepAliveTime,控制如何銷毀線程。
allowCoreThreadTimeOut:
該屬性用來控制是否允許核心線程超時退出。If false,core threads stay alive even when idle.If true, core threads use keepAliveTime to time out waiting for work。如果線程池的大小已經達到了corePoolSize,不管有沒有任務需要執行,線程池都會保證這些核心線程處於存活狀態。可以知道:該屬性只是用來控制核心線程的。
keepAliveTime:
如果一個線程處在空閑狀態的時間超過了該屬性值,就會因為超時而退出。舉個例子,如果線程池的核心大小corePoolSize=5,而當前大小poolSize =8,那么超出核心大小的線程,會按照keepAliveTime的值判斷是否會超時退出。如果線程池的核心大小corePoolSize=5,而當前大小poolSize =5,那么線程池中所有線程都是核心線程,這個時候線程是否會退出,取決於allowCoreThreadTimeOut。
Runnable getTask() { for (;;) { try { int state = runState; if (state > SHUTDOWN) return null; Runnable r; if (state == SHUTDOWN) // Help drain queue
r = workQueue.poll(); else if (poolSize > corePoolSize || allowCoreThreadTimeOut) r = workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS); else r = workQueue.take(); if (r != null) return r; if (workerCanExit()) { if (runState >= SHUTDOWN) // Wake up others
interruptIdleWorkers(); return null; } // Else retry
} catch (InterruptedException ie) { // On interruption, re-check runState
} } }
(poolSize > corePoolSize || allowCoreThreadTimeOut)這個條件,就是用來判斷是否允許當前線程退出。workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS);就是借助阻塞隊列,讓空閑線程等待keepAliveTime時間之后,恢復執行。這樣空閑線程會由於超時而退出。
參考:
http://blog.csdn.net/aitangyong/article/details/38822505
http://www.cnblogs.com/kuoAT/p/6714762.html