本文同時載于本人CSDN
1.概述
使用Thread類執(zhí)行某個任務床估,任務運行時每次都要創(chuàng)建線程雇寇,任務結束則要銷毀線程,對系統(tǒng)而言虽另,線程不僅是資源暂刘,線程的創(chuàng)建與銷毀也要消耗系統(tǒng)的資源。對于此問題捂刺,很顯然一個直接的解決方案就是復用這個線程谣拣,讓線程執(zhí)行完某個任務后仍然能繼續(xù)執(zhí)行執(zhí)行其它任務,而不是銷毀族展。線程池就提供了這樣的解決方案芝发。
線程池指的是存在一組線程,這組線程創(chuàng)建后一般不會銷毀苛谷,而是進入休眠狀態(tài)辅鲸,等到有新的任務需要執(zhí)行又重新恢執(zhí)行。
2.創(chuàng)建一個線程池
java.util.concurrent(JUC)包提供了很多接口用于線程池的創(chuàng)建腹殿。
2.1 ExecutorService 接口
線程池通常指的是ExecutorService
—— 一個提供了終止管理的方法和可以生成Future的方法的Executor
独悴。簡單地說例书,ExecutorService
接口繼承了Executor
接口,ExecutorService
除了存在Executor
提供的execute方法外刻炒,還提供了很多終止管理的方法以及產生用于跟蹤線程執(zhí)行結果的Future的submit决采。如下所示
boolean awaitTermination(long timeout, TimeUnit unit)
<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit)
<T> T invokeAny(Collection<? extends Callable<T>> tasks)
<T> T invokeAny(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit)
boolean isShutdown()
boolean isTerminated()
void shutdown()
List<Runnable> shutdownNow()
<T> Future<T> submit(Callable<T> task)
Future<?> submit(Runnable task)
<T> Future<T> submit(Runnable task, T result)
2.2 直接通過 ThreadPoolExecutor創(chuàng)建線程池
JUC提供了ThreadPoolExecutor類用于創(chuàng)建線程池,這個類繼承了實現ExecutorService接口的AbstractThreadPoolExecutor坟奥。ThreadPoolExecutor有若干個構造參數树瞭,一般可以通過調用如下所示的創(chuàng)建一個線程池:
ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue)
代碼如下
ExecutorService executor = new TreadPoolExecutor(5, 10, 10L,TimeUnit.SECONDS,new BlockingArrayQueue(40));
其中,各個參數的含義如下:
-
corePoolSize
線程池中保持存活的線程數量爱谁,即使線程是空閑的晒喷,除非調用allowCoreThreadTimeOut(true) -
corePoolSize
線程池中最大能創(chuàng)建的線程數量 -
keepAliveTime
空閑線程的存活時間 -
unit
KeepAliveTime參數的單位例如 TimeUnit.SECONDES等 -
workQueue
一個儲存所提交的runnable的任務的隊列,只會execute方法提交的runnable任務
通過上面的參數描述访敌,我們可以知道凉敲,通過ThreadPoolExecutor創(chuàng)建的的線程池是可以自動調整大小的。當一個新的任務提交時寺旺,如果此時線程池中的線程小于corePoolSize
爷抓,那么線程池就會創(chuàng)建一個新的線程來執(zhí)行此任務,即使其它的線程處于空閑狀態(tài)阻塑。如果此時線程池中存在大于corePoolSize
但是小于maximumPoolSize
的線程數蓝撇,除非workQueue
是滿的,否則不會創(chuàng)建新的線程陈莽。因此唉地,我們可以通過設置corePoolSize
和maximumPoolSize
相等來創(chuàng)建一個固定線程數的線程池,也可以通過設置一個實質上沒有邊界的的值例如Integer.MAX_VALUE
來創(chuàng)建可以容納任意并發(fā)線程數的線程池传透。一般來說,corePoolSize
和maximumPoolSize
在線程池構造時就已經確定极颓,但是我們也可以通過 setCorePoolSize(int)
和 setMaximumPoolSize(int)
動態(tài)設置這兩個值朱盐。
2.3 線程池中線程的創(chuàng)建
線程池使用ThreadFactory創(chuàng)建新線程。如果沒有另外指定菠隆,則使用Executors.defaultThreadFactory()
兵琳,它將所有線程創(chuàng)建在同一個ThreadGroup中,并具有相同的NORM_PRIORITY
優(yōu)先級和非守護進程狀態(tài)骇径。 通過提供不同的ThreadFactory躯肌,我們可以更改線程的名稱,線程組破衔,優(yōu)先級清女,守護程序狀態(tài)等。如果ThreadFactory
調用newThread時返回null
創(chuàng)建線程失敗晰筛,則executor將繼續(xù)嫡丙,但可能無法執(zhí)行任何任務拴袭。 線程應該擁有“modifyThread”的RuntimePermission。如果使用線程池的工作線程或其他線程不具有此權限曙博,則服務可能會降級:配置更改可能不會及時生效拥刻,并且關閉線程池可能保持可以終止但未完成的狀態(tài)。
2.4 線程存活時間
如果池當前具有多于corePoolSize
的線程父泳,則多余線程如果空閑時間超過keepAliveTime
般哼,則將終止(請參閱getKeepAliveTime(TimeUnit)
)。 這提供了一種在不使用池時減少資源消耗的方法惠窄。如果線程池稍后變得更活躍蒸眠,則將構造新線程。 也可以使用方法setKeepAliveTime(long睬捶,TimeUnit)動態(tài)更改此參數黔宛。使用Long.MAX_VALUE
TimeUnit.NANOSECONDS
這樣的值將有效地禁止空閑線程在線程池關閉之前終止。 默認情況下擒贸,僅當存在大于corePoolSize的線程時臀晃,保持活動策略才適用。但是方法allowCoreThreadTimeOut(boolean)
也可用于將此超時策略應用于核心線程介劫,只要keepAliveTime值不為零徽惋。
2.5 工作隊列
任何BlockingQueue都可用于傳輸和保存提交的任務。此隊列的使用與池大小相互影響:
- 如果運行的線程少于corePoolSize座韵,則Executor總是更喜歡添加新線程而不是將任務加入隊列险绘。
- 如果corePoolSize或更多線程正在運行,則Executor總是更喜歡排隊請求而不是添加新線程誉碴。
- 如果隊列已經滿了宦棺,則會創(chuàng)建一個新線程,除非它超過maximumPoolSize黔帕,在這種情況下代咸,該任務將被拒絕。
隊列選擇有三種常規(guī)策略:
- 直接交接成黄。工作隊列的一個很好的默認選擇是
SynchronousQueue
呐芥,它將任務交給線程而不另外保存它們。如果沒有線程立即可用于執(zhí)行任務奋岁,則嘗試添加一個新的任務將會失敗思瘟,因此將構造新線程。此策略在處理可能具有內部依賴性的請求集時避免了鎖定闻伶。直接交接法通常需要無邊界的maximumPoolSizes
以避免拒絕新提交的任務滨攻。這也就意味著,當任務以比處理它們更快的速度到達時,線程可能會無限制增長铡买。 - 無限隊列更鲁。使用無限制隊列(例如,沒有預定義容量的LinkedBlockingQueue)將導致新任務在沒有空閑線程時在隊列中等待奇钞。因此澡为,線程池只會創(chuàng)建corePoolSize數量的線程(
maximumPoolSize
的值因此沒有任何效果)。當每個任務完全獨立于其他任務時景埃,這可能是適當的媒至,因此任務不會影響彼此的執(zhí)行;例如,在網頁服務器中谷徙。雖然這種排隊方式可以有助于平滑瞬態(tài)突發(fā)請求拒啰,但是這同時意味著當任務繼續(xù)平均到達的速度超過可處理速度時,工作隊列可能會無限制增長完慧。 - 有限的隊列谋旦。有限隊列(例如,ArrayBlockingQueue)與有限maximumPoolSizes一起使用時有助于防止資源耗盡屈尼,但可能更難以調整和控制册着。隊列大小和最大池大小之間可以相互折中(trade off):使用大型隊列和小型池可以最小化CPU使用率,OS資源和上下文切換開銷脾歧,但可能導致人為的低吞吐量甲捏。如果任務經常阻塞(例如,如果它們執(zhí)行時存在I/O操作)鞭执,則系統(tǒng)可能能夠提供比
maximumPoolSizes
更多的線程調度時間司顿。使用小隊列通常需要更大的池大小,這會使CPU更加繁忙兄纺,但可能會遇到不可接受的調度開銷大溜,這也會降低吞吐量。
2.6 任務提交
提交任務有兩種方法估脆,一種是提交無返回值的任務钦奋,使用如下方式:
ExecutorService executor = new TreadPoolExecutor(5, 10, 10L,TimeUnit.SECONDS,new BlockingArrayQueue(40));
Runnable handler = ......;
executor.execute(handler);
如果需要獲得任務的執(zhí)行結果,則可以通過調用submit方法來提交任務旁蔼,代碼如下:
ExecutorService executor = new TreadPoolExecutor(5, 10, 10L,TimeUnit.SECONDS,new BlockingArrayQueue(40));
Callable handler = ......;
Future future = executor.submit(handler);
2.7 創(chuàng)建線程池的完整實例
import java.util.concurrent.*;
public TreadPoolTest(){
public static void main(String[] args){
ExecutorService executor = new TreadPoolExecutor(5, 10, 10L,TimeUnit.SECONDS,new ArrayBlockingQueue(40));
for(int i = 0;i < 10; i++){
Handler handler = new Handler();
executor.execute(handler);
}
executor.shutdown();
}
class Handler extends Runnable{
public void run(){
for(int i = 0; i < 5;i++){
System.out.println(i);
}
}
}
}
3 通過Executor的工廠方法創(chuàng)建線程池
Executors是JUC包下的一個工廠類,他提供了許多便捷的方式創(chuàng)建線程池疙教。
3.1 創(chuàng)建固定大小的線程池
可以通過Executors類創(chuàng)建一個固定大小的線程池:
ExecutorService executor = Executors.newFixedThreadPool(10);
我們前面已經說過棺聊,固定大小的線程池可以通過設置ThreadPoolExecutor 核心線程數和最大線程數相等實現,事實上源碼也是如此的:
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
一般來說贞谓,當線程數達到核心線程數限佩,既不會再創(chuàng)建線程,也不會銷毀線程,除非發(fā)生異常某個線程終止,或者設置allowCoreTimeOut祟同,但最好別這么做作喘,因為keepAlive參數此時被設置為0,可能會導致頻繁的創(chuàng)建與銷毀線程(這里僅僅是推測。仍需驗證)晕城。
3.2 創(chuàng)建可緩存的線程池:
可以通過Executors.newCachedThreadPool
創(chuàng)建一個可緩存的線程池泞坦,如果當前線程池的規(guī)模超出了處理需求,將回收空的線程砖顷;當需求增加時贰锁,會增加線程數量;線程池規(guī)模無限制滤蝠。代碼如下:
ExecutorService executor = Executors.newCachedThreadPool(10);
前面同樣也提到豌熄,可以設置maxmumPoolSize
參數為一個實質上無邊界的值如Integer.MAX_VALUE
讓線程池的規(guī)模無限制,實際上源碼也是如此實現的:
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
SynchronousQueue<Runnable>());
}
3.3 創(chuàng)建單線程的線程池
可以通過Executors.newSingleThreadExecutor()
創(chuàng)建一個單線程的Executor物咳,確保任務串行執(zhí)行:
ExecutorService executor = Executors.newSingleExecutor();
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
4 總結
這篇文章深入描述線程池的創(chuàng)建方法者蠕,各個參數的意義與作用儿普,但是線程池除以上所描述的,還存在可調度的線程池,在本系列的下一篇文章將講述這一類線程池逞壁。