一、爲什麼要用線程池?
1.減少了創建和銷燬線程的次數,每個工作線程都可以被重複利用,可執行多個任務。
2.可以根據系統的承受能力,調整線程池中工作線線程的數目,防止因爲消耗過多的內存,而把服務器累趴下(每個線程需要大約1MB內存,線程開的越多,消耗的內存也就越大,最後死機)。
Java裏面線程池的頂級接口是Executor,但是嚴格意義上講Executor並不是一個線程池,而只是一個執行線程的工具。真正的線程池接口是ExecutorService
執行一個異步任務你還只是如下new Thread嗎?
那你就out太多了,new Thread的弊端如下:
1.每次new Thread新建對象性能差。
2.線程缺乏統一管理,可能無限制新建線程,相互之間競爭,及可能佔用過多系統資源導致死機或oom。
3.缺乏更多功能,如定時執行、定期執行、線程中斷。
相比new Thread,Java提供的四種線程池的好處在於:
1.重用存在的線程,減少對象創建、消亡的開銷,性能佳。
2.可有效控制最大併發線程數,提高系統資源的使用率,同時避免過多資源競爭,避免堵塞。
3.提供定時執行、定期執行、單線程、併發數控制等功能。
二、Executor的框架圖
接口:Executor,CompletionService,ExecutorService,ScheduledExecutorService
抽象類:AbstractExecutorService
實現類:ExecutorCompletionService,ThreadPoolExecutor,ScheduledThreadPoolExecutor
ExecutorService
真正的線程池接口。
ScheduledExecutorService
能和Timer/TimerTask類似,解決那些需要任務重複執行的問題。
ThreadPoolExecutor
ExecutorService的默認實現。
ScheduledThreadPoolExecutor
繼承ThreadPoolExecutor的ScheduledExecutorService接口實現,週期性任務調度的類實現
三、Executors提供四種線程池
newCachedThreadPool創建一個可緩存線程池,如果線程池長度超過處理需要,可靈活回收空閒線程,若無可回收,則新建線程。
newFixedThreadPool 創建一個定長線程池,可控制線程最大併發數,超出的線程會在隊列中等待。
newScheduledThreadPool 創建一個定長線程池,支持定時及週期性任務執行。
newSingleThreadExecutor 創建一個單線程化的線程池,它只會用唯一的工作線程來執行任務,保證所有任務按照指定順序(FIFO, LIFO, 優先級)執行
創建四種線程池方式:https://www.cnblogs.com/lanseyitai1224/p/7895652.html
四、ThreadPoolExecutor
ThreadPoolExecutor是ExecutorService的一個實現類,它使用可能的幾個池線程之一執行每個提交的任務,通常使用 Executors 工廠方法配置。
ThreadPoolExecutor構造函數:
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
threadFactory, defaultHandler);
}
除了ScheduledThreadPool,Executors都是通過該構造方法來創建其他三種不同的線程池的
corePoolSize: 線程池維護線程的最少數量
如果執行了線程池的prestartAllCoreThreads()方法,線程池會提前創建並啓動所有核心線程。
maximumPoolSize:線程池允許的最大線程數,他表示最大能創建多少個線程。maximumPoolSize肯定是大於等於corePoolSize
keepAliveTime: 表示線程沒有任務時最多保持多久然後停止。默認情況下,只有線程池中線程數大於corePoolSize 時,keepAliveTime 纔會起作用。換句話說,當線程池中的線程數大於corePoolSize,並且一個線程空閒時間達到了keepAliveTime,那麼就是shutdown。
unit: 線程池維護線程所允許的空閒時間的單位 ,keepAliveTime 的單位。
workQueue: 線程池所使用的緩衝隊列
一個阻塞隊列,用來存儲等待執行的任務,當線程池中的線程數超過它的corePoolSize的時候,線程會進入阻塞隊列進行阻塞等待。通過workQueue,線程池實現了阻塞功能
handler: 線程池對拒絕任務的處理策略
threadFactory :線程工廠,用來創建線程。
一個任務通過 execute(Runnable)方法被添加到線程池,任務就是一個 Runnable類型的對象,任務的執行方法就是 Runnable類型對象的run()方法。
當一個任務通過execute(Runnable)方法欲添加到線程池時:
如果此時線程池中的數量小於corePoolSize,即使線程池中的線程都處於空閒狀態,也要創建新的線程來處理被添加的任務。
如果此時線程池中的數量等於 corePoolSize,但是緩衝隊列 workQueue未滿,那麼任務被放入緩衝隊列。
如果此時線程池中的數量大於corePoolSize,緩衝隊列workQueue滿,並且線程池中的數量小於maximumPoolSize,建新的線程來處理被添加的任務。
如果此時線程池中的數量大於corePoolSize,緩衝隊列workQueue滿,並且線程池中的數量等於maximumPoolSize,那麼通過 handler所指定的策略來處理此任務。
也就是:處理任務的優先級爲:
核心線程corePoolSize、任務隊列workQueue、最大線程maximumPoolSize,如果三者都滿了,使用handler處理被拒絕的任務。
當線程池中的線程數量大於 corePoolSize時,如果某線程空閒時間超過keepAliveTime,線程將被終止。這樣,線程池可以動態的調整池中的線程數。
unit可選的參數爲java.util.concurrent.TimeUnit中的幾個靜態屬性:
NANOSECONDS、MICROSECONDS、MILLISECONDS、SECONDS。
workQueue我常用的是:java.util.concurrent.ArrayBlockingQueue
五、線程池四種拒絕策略
- AbortPolicy:丟棄任務並拋出RejectedExecutionException
- CallerRunsPolicy:只要線程池未關閉,該策略直接在調用者線程中,運行當前被丟棄的任務。顯然這樣做不會真的丟棄任務,但是,任務提交線程的性能極有可能會急劇下降。
- DiscardOldestPolicy:丟棄隊列中最老的一個請求,也就是即將被執行的一個任務,並嘗試再次提交當前任務。
- DiscardPolicy:丟棄任務,不做任何處理
六、線程池的任務處理策略
如果當前線程池中的線程數目小於corePoolSize,則每來一個任務,就會創建一個線程去執行這個任務;
如果當前線程池中的線程數目>=corePoolSize,則每來一個任務,會嘗試將其添加到任務緩存隊列當中,若添加成功,則該任務會等待空閒線程將其取出去執行;若添加失敗(一般來說是任務緩存隊列已滿),則會嘗試創建新的線程去執行這個任務;如果當前線程池中的線程數目達到maximumPoolSize,則會採取任務拒絕策略進行處理;
如果線程池中的線程數量大於 corePoolSize時,如果某線程空閒時間超過keepAliveTime,線程將被終止,直至線程池中的線程數目不大於corePoolSize;如果允許爲核心池中的線程設置存活時間,那麼核心池中的線程空閒時間超過keepAliveTime,線程也會被終止。
七、線程池的關閉
ThreadPoolExecutor提供了兩個方法,用於線程池的關閉,分別是shutdown()和shutdownNow(),其中:
shutdown():不會立即終止線程池,而是要等所有任務緩存隊列中的任務都執行完後才終止,但再也不會接受新的任務
shutdownNow():立即終止線程池,並嘗試打斷正在執行的任務,並且清空任務緩存隊列,返回尚未執行的任務
八、如何選擇線程池數量
線程池的大小決定着系統的性能,過大或者過小的線程池數量都無法發揮最優的系統性能。
當然線程池的大小也不需要做的太過於精確,只需要避免過大和過小的情況。一般來說,確定線程池的大小需要考慮CPU的數量,內存大小,任務是計算密集型還是IO密集型等因素
NCPU = CPU的數量
UCPU = 期望對CPU的使用率 0 ≤ UCPU ≤ 1
W/C = 等待時間與計算時間的比率
如果希望處理器達到理想的使用率,那麼線程池的最優大小爲:
線程池大小=NCPU *UCPU(1+W/C)
引用: