並發編程(二):線程池
相比於線程池,我們可能接觸new Thread更多一點,既然有了new Thread我們為什麽還要使用線程池呢?
new Thread的弊端
a、每次new Thread新建對象,性能差
b、線程缺乏統一管理,可能無限制的新建線程,相互競爭,有可能占用過多系統資源導致死機或者OOM(OutOfMemory)
c、缺少更多功能,如更多執行、定期執行、線程中斷
線程池的優勢
a、重用存在的線程,減少對象創建、消亡的開銷,性能好
b、可有效控制最大並發線程數,提高系統資源利用率,同時可以避免過多資源競爭,避免阻塞
c、提供定時執行、定期執行、單線程、並發數控制等功能
線程池類圖
由類圖可以看出,ThreadPoolExecutor類是線程池中最核心的一個類。我們來做重點分析
ThreadPoolExecutor構造參數
a、corePoolSize:核心線程數量,默認情況下(可預創建線程)線程池後線程池中的線程數為0,當有任務後當有任務後就會創建一個線程去執行任務,當線程池中的線程數目達到corePoolSize後,就會把到達的任務放到緩存隊列中
b、maximumPoolSize:線程最大線程數
c、workQueue:阻塞隊列,存儲等待執行的任務,有三種取值,ArrayBlockQueue(基於數組的先進先出隊列,創建時必須指定大小)、LinkedBlockingQueue(基於鏈表的先進先出隊列,如果沒有指定此隊列大小,默認為Integer.MAX_VALUE)、SynchronousQueue(不會保存提交的任務,直接新建一個線程來執行新的任務)
d、keepAliveTime:線程沒有任務執行時最多保持多久時間終止,默認情況只有當線程池中的線程數大於corePoolSize時,keepAliveTIme才會起作用,當線程池中的線程數大於corePoolSize,如果一個線程的空閑時間達到keepAliveTime,則會終止。如果調用allowCoreThreadTimeOut(boolean)方法,在線程池中的線程數不大於corePoolSize時keepAliveTime參數也會起作用,直到線程池的線程數為0
e、unit:keepAliveTime的時間單位,有7中取值,如:TimeUnit.DAYS; 天,可具體到納秒
f、threadFactory:線程工廠,用來創建線程
g、rejectHandler:當拒絕處理任務時的策略,通常有四種取值,ThreadPoolExecutor.AbortPolicy:丟棄任務並拋出RejectedExecutionException異常;ThreadPoolExecutor.DiscardPolicy:丟棄任務,但不拋出異常;ThreadPoolExecutor.DiscardOldestPolicy:丟棄隊列最前面的任務,重新嘗試執行任務(重復此過程);ThreadPoolExecutor.CallerRunsPolicy:由調用線程處理該任務
參數之間的關系如下:
a、如果當前poolsize小於corePoolSize,創建新線程執行任務
b、如果當前poolsize大於corePoolsize,且等待隊列未滿,進入等待隊列
c、如果當前poolsize大於corePoolsize且小於maximumPoolSize,且等待隊列已滿,創建新線程執行任務
d、如果當前poolsize大於corePoolSize且大於maximumPoolSize,且等待隊列已滿則用拒絕策略來處理該任務
e、線程池中的線程執行完任務後不會立刻退出,而是去檢查等待隊列是否有新的線程去執行,如果在keepAliveTime裏等不到新任務,線程就會退出
ThreadPoolExecutor狀態
圖中需要註意的是shutdown()和shutdownNow()方法的區別,執行前者後,還在執行的線程會執行完再關閉,執行後者後,線程池會立刻關閉,正在執行的線程不再執行
ThreadPoolExecutor方法
a、execute():提交任務,交給線程池執行
b、submit():提交任務,能夠返回執行結果 execute+Future
c、shutdown():關閉線程池,等待任務都執行完
d、shutdownNow():關閉線程池,不等待任務執行完
e、getTaskCount():線程池已執行的和未執行的任務總數
f、getCompletedTaskCount():已完成的任務數量
g、getPoolSize():線程池當前線程數量
h、getActiveCount():當前線程池正在執行任務的線程數量
Executors類
由上面的類圖可以看出,Executors類為Executor,ExecutorService,ScheduledExecutorService提供了一些工具方法,並且Executors封裝了ScheduledThreadPoolExecutor類和ThreadPoolExecutor類,所以我們倡導在實際使用中使用此類,Executors類提供了四個靜態方法:
a、newCachedThreadPool:創建可緩存線程池,靈活回收空閑線程,如果沒有可回收線程,則創建新線程
由上圖可看出,newCachedThreadPool將corePoolSize設置為0,將maximumPoolSize設置為Integer.MAX_VALUE,使用SynchronousQueue,就是說有新任務就創新線程運行,線程空閑超過60秒,則銷毀線程。demo代碼如下:
public static void main(String[] args) { ExecutorService executorService = Executors.newCachedThreadPool(); for (int i = 0; i < 10; i++) { final int index = i; executorService.execute(new Runnable() { @Override public void run() { log.info("task:{}", index); } }); } executorService.shutdown(); }
b、newFixedThreadPool:創建定長線程池,可控制線程最大並發數,超出的線程會在隊列中等待
newFixedThreadPool創建的線程池corePoolSize和maximumPoolSize值相等,線程空閑後直接銷毀,使用的是LinkedBlockingQueue。demo代碼如下:
public static void main(String[] args) { ExecutorService executorService = Executors.newFixedThreadPool(3); for (int i = 0; i < 10; i++) { final int index = i; executorService.execute(new Runnable() { @Override public void run() { log.info("task:{}", index); } }); } executorService.shutdown(); }
c、newScheduledThreadPool:創建大小無限制線程池(最大為Integer.MAX_VALUE),支持定時及周期性任務執行
newScheduledThreadPool的特點是可以進行任務調度,最常用的方法是ScheduleAtFixedRate(基於固定時間間隔進行任務調度)和ScheduleWithFixedDelay(基於不固定時間間隔進行任務調度,主要取決於任務執行時間長短), demo代碼如下:
public static void main(String[] args) { ScheduledExecutorService executorService = Executors.newScheduledThreadPool(5); executorService.schedule(new Runnable() { @Override public void run() { log.warn("schedule run"); } },3,TimeUnit.SECONDS); executorService.scheduleAtFixedRate(new Runnable() { @Override public void run() { log.warn("schedule run"); } },1,3,TimeUnit.SECONDS); }
d、newSingleThreadExecutor:創建一個單線程化的線程池,只會用唯一的線程來執行任務
newSingleThreadExecutor將corePoolSize和maximumPoolSize都固定為1,線程空閑時直接銷毀,使用的LinkedBlockingQueue.demo代碼如下:
public static void main(String[] args) { ExecutorService executorService = Executors.newSingleThreadExecutor(); for (int i = 0; i < 10; i++) { final int index = i; executorService.execute(new Runnable() { @Override public void run() { log.info("task:{}", index); } }); } executorService.shutdown(); }
線程池合理配置
線程池的具體配合需要按照實際情況進行調整,以下為兩條參考原則,可先按此原則設置再根據系統負載,資源利用情況進行調整。
a、cpu密集型任務,需要盡量壓榨cpu,參考值可以設置為ncpu+1;
b、io密集型任務,參考值可以設置為2*ncpu
並發編程(二):線程池