摘要:任務(wù)性質(zhì)不同的任務(wù)可以用不同規(guī)模的線程池分開處理。線程池在運行過程中已完成的任務(wù)數(shù)量。如等于線程池的最大大小,則表示線程池曾經(jīng)滿了。線程池的線程數(shù)量。獲取活動的線程數(shù)。通過擴展線程池進行監(jiān)控??蚣馨ň€程池,,,,,,等。
Java線程池
[toc]
什么是線程池線程池就是有N個子線程共同在運行的線程組合。
舉個容易理解的例子:有個線程組合(即線程池,咱可以比喻為一個公司),里面有3個子線程(當(dāng)作3個員工吧),待命干活。
只要客戶告訴他一個任務(wù)(比如搬磚),公司就會挑一個員工來做;
如果很多客戶都找,3個忙不過來,那公司可以再雇2個人,但本公司運營能力有限,辦公室也不大,最多就雇傭5個人,如果還忙不過來,那這些送來的任務(wù)就排隊了。一件一件做完。
ThreadPoolExecutor簡介java.uitl.concurrent.ThreadPoolExecutor類是線程池中最核心的一個類,因此如果要透徹地了解Java中的線程池,必須先了解這個類。下面我們來看一下ThreadPoolExecutor類的具體實現(xiàn)源碼:
在ThreadPoolExecutor類中提供了四個構(gòu)造方法:
public class ThreadPoolExecutor extends AbstractExecutorService { ..... public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit, BlockingQueueworkQueue); public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit, BlockingQueue workQueue,ThreadFactory threadFactory); public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit, BlockingQueue workQueue,RejectedExecutionHandler handler); public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler); }
從上面的代碼可以得知,ThreadPoolExecutor繼承了AbstractExecutorService類,并提供了四個構(gòu)造器,事實上,通過觀察每個構(gòu)造器的源碼具體實現(xiàn),發(fā)現(xiàn)前面三個構(gòu)造器都是調(diào)用的第四個構(gòu)造器進行的初始化工作。
corePoolSize 線程池維護線程的最少數(shù)量。
需要注意的是在初創(chuàng)建線程池時線程不會立即啟動,直到有任務(wù)提交才開始啟動線程并逐漸時線程數(shù)目達(dá)到corePoolSize。若想一開始就創(chuàng)建所有核心線程需調(diào)用prestartAllCoreThreads方法。
maximumPoolSize-池中允許的最大線程數(shù)。
需要注意的是當(dāng)核心線程滿且阻塞隊列也滿時才會判斷當(dāng)前線程數(shù)是否小于最大線程數(shù),并決定是否創(chuàng)建新線程。
keepAliveTime - 線程池維護線程所允許的空閑時間
當(dāng)線程數(shù)大于核心時,多于的空閑線程最多存活時間
默認(rèn)情況下,只有當(dāng)線程池中的線程數(shù)大于corePoolSize時,keepAliveTime才會起作用,直到線程池中的線程數(shù)不大于corePoolSize,即當(dāng)線程池中的線程數(shù)大于corePoolSize時,如果一個線程空閑的時間達(dá)到keepAliveTime,則會終止,直到線程池中的線程數(shù)不超過corePoolSize。但是如果調(diào)用了allowCoreThreadTimeOut(boolean)方法,在線程池中的線程數(shù)不大于corePoolSize時,keepAliveTime參數(shù)也會起作用,直到線程池中的線程數(shù)為0
unit - keepAliveTime 參數(shù)的時間單位,有7種取值。
TimeUnit.DAYS; //天 TimeUnit.HOURS; //小時 TimeUnit.MINUTES; //分鐘 TimeUnit.SECONDS; //秒 TimeUnit.MILLISECONDS; //毫秒 TimeUnit.MICROSECONDS; //微妙 TimeUnit.NANOSECONDS; //納秒
workQueue - 當(dāng)線程數(shù)目超過核心線程數(shù)時用于保存任務(wù)的隊列。主要有3種類型的BlockingQueue可供選擇:有界隊列,無界隊列和同步移交。
ArrayBlockingQueue; //有界隊列 LinkedBlockingQueue; //無界隊列 SynchronousQueue; //同步移交 PriorityBlockingQueue; //一個具有優(yōu)先級得無限阻塞隊列。
threadFactory - 執(zhí)行程序創(chuàng)建新線程時使用的工廠。
handler - 阻塞隊列已滿且線程數(shù)達(dá)到最大值時所采取的飽和策略。java默認(rèn)提供了4種飽和策略的實現(xiàn)方式:中止、拋棄、拋棄最舊的、調(diào)用者運行。
ThreadPoolExecutor.AbortPolicy(); 拋出java.util.concurrent.RejectedExecutionException異常 ThreadPoolExecutor.CallerRunsPolicy(); 重試添加當(dāng)前的任務(wù),他會自動重復(fù)調(diào)用execute()方法 ThreadPoolExecutor.DiscardOldestPolicy(); 拋棄舊的任務(wù) ThreadPoolExecutor.DiscardPolicy(); 拋棄當(dāng)前的任務(wù) 當(dāng)然也可以根據(jù)應(yīng)用場景需要來實現(xiàn)`RejectedExecutionHandler`接口自定義策略。如記錄日志或持久化不能處理的任務(wù)。向上翻源碼
從上面給出的ThreadPoolExecutor類的代碼可以知道,ThreadPoolExecutor繼承了AbstractExecutorService,我們來看一下AbstractExecutorService的實現(xiàn):
public abstract class AbstractExecutorService implements ExecutorService { protectedRunnableFuture newTaskFor(Runnable runnable, T value) { }; protected RunnableFuture newTaskFor(Callable callable) { }; public Future> submit(Runnable task) {}; public Future submit(Runnable task, T result) { }; public Future submit(Callable task) { }; private T doInvokeAny(Collection extends Callable > tasks, boolean timed, long nanos) throws InterruptedException, ExecutionException, TimeoutException { }; public T invokeAny(Collection extends Callable > tasks) throws InterruptedException, ExecutionException { }; public T invokeAny(Collection extends Callable > tasks, long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { }; public List > invokeAll(Collection extends Callable > tasks) throws InterruptedException { }; public List > invokeAll(Collection extends Callable > tasks, long timeout, TimeUnit unit) throws InterruptedException { }; }
AbstractExecutorService是一個抽象類,它實現(xiàn)了ExecutorService接口。
我們接著看ExecutorService接口的實現(xiàn):
public interface ExecutorService extends Executor { void shutdown(); boolean isShutdown(); boolean isTerminated(); boolean awaitTermination(long timeout, TimeUnit unit)throws InterruptedException;Future submit(Callable task); Future submit(Runnable task, T result); Future> submit(Runnable task); List > invokeAll(Collection extends Callable > tasks)throws InterruptedException; List > invokeAll(Collection extends Callable > tasks,long timeout, TimeUnit unit)throws InterruptedException; T invokeAny(Collection extends Callable > tasks)throws InterruptedException, ExecutionException; T invokeAny(Collection extends Callable > tasks,long timeout, TimeUnit unit)throws InterruptedException, ExecutionException, TimeoutException; }
而ExecutorService又是繼承了Executor接口,我們看一下Executor接口的實現(xiàn):
public interface Executor { void execute(Runnable command); }
到這里,大家應(yīng)該明白了ThreadPoolExecutor、AbstractExecutorService、ExecutorService和Executor幾個之間的關(guān)系了。
Executor是一個頂層接口,在它里面只聲明了一個方法execute(Runnable),返回值為void,參數(shù)為Runnable類型,從字面意思可以理解,就是用來執(zhí)行傳進去的任務(wù)的;
然后ExecutorService接口繼承了Executor接口,并聲明了一些方法:submit、invokeAll、invokeAny以及shutDown等;
抽象類AbstractExecutorService實現(xiàn)了ExecutorService接口,基本實現(xiàn)了ExecutorService中聲明的所有方法;
在ThreadPoolExecutor類中有幾個非常重要的方法:
execute() submit() shutdown() shutdownNow()
execute()方法實際上是Executor中聲明的方法,在ThreadPoolExecutor進行了具體的實現(xiàn),這個方法是ThreadPoolExecutor的核心方法,通過這個方法可以向線程池提交一個任務(wù),交由線程池去執(zhí)行。
submit()方法是在ExecutorService中聲明的方法,在AbstractExecutorService就已經(jīng)有了具體的實現(xiàn),在ThreadPoolExecutor中并沒有對其進行重寫,這個方法也是用來向線程池提交任務(wù)的,但是它和execute()方法不同,它能夠返回任務(wù)執(zhí)行的結(jié)果,去看submit()方法的實現(xiàn),會發(fā)現(xiàn)它實際上還是調(diào)用的execute()方法,只不過它利用了Future來獲取任務(wù)執(zhí)行結(jié)果。
shutdown()和shutdownNow()是用來關(guān)閉線程池的。
還有很多其他的方法比如:getQueue() 、getPoolSize()、getActiveCount()、getCompletedTaskCount()等獲取與線程池相關(guān)屬性的方法,自行查閱API。
線程池的流程分析線程池的主要工作流程如下圖:
從上圖我們可以看出,當(dāng)提交一個新任務(wù)到線程池時,線程池的處理流程如下:
首先線程池判斷基本線程池是否已滿?沒滿,創(chuàng)建一個工作線程來執(zhí)行任務(wù)。滿了,則進入下個流程。
其次線程池判斷工作隊列是否已滿?沒滿,則將新提交的任務(wù)存儲在工作隊列里。滿了,則進入下個流程。
最后線程池判斷整個線程池是否已滿?沒滿,則創(chuàng)建一個新的工作線程來執(zhí)行任務(wù),滿了,則交給飽和策略來處理這個任務(wù)。
源碼分析線程池執(zhí)行任務(wù)的方法如下:
public void execute(Runnable command) { if (command == null) throw new NullPointerException(); //如果線程數(shù)小于基本線程數(shù),則創(chuàng)建線程并執(zhí)行當(dāng)前任務(wù) if (poolSize >= corePoolSize || !addIfUnderCorePoolSize(command)) { //如線程數(shù)大于等于基本線程數(shù)或線程創(chuàng)建失敗,則將當(dāng)前任務(wù)放到工作隊列中。 if (runState == RUNNING && workQueue.offer(command)) { if (runState != RUNNING || poolSize == 0) ensureQueuedTaskHandled(command); } //如果線程池不處于運行中或任務(wù)無法放入隊列,并且當(dāng)前線程數(shù)量小于最大允許的線程數(shù)量,則創(chuàng)建一個線程執(zhí)行任務(wù)。 else if (!addIfUnderMaximumPoolSize(command)) //拋出RejectedExecutionException異常 reject(command); // is shutdown or saturated } }
工作線程。線程池創(chuàng)建線程時,會將線程封裝成工作線程Worker,Worker在執(zhí)行完任務(wù)后,還會無限循環(huán)獲取工作隊列里的任務(wù)來執(zhí)行。我們可以從Worker的run方法里看到這點:
public void run() { try { Runnable task = firstTask; firstTask = null; while (task != null || (task = getTask()) != null) { runTask(task); task = null; } } finally { workerDone(this); } }合理的配置線程池
要想合理的配置線程池,就必須首先分析任務(wù)特性,可以從以下幾個角度來進行分析:
任務(wù)的性質(zhì):CPU密集型任務(wù),IO密集型任務(wù)和混合型任務(wù)。
任務(wù)的優(yōu)先級:高,中和低。
任務(wù)的執(zhí)行時間:長,中和短。
任務(wù)的依賴性:是否依賴其他系統(tǒng)資源,如數(shù)據(jù)庫連接。
任務(wù)性質(zhì)不同的任務(wù)可以用不同規(guī)模的線程池分開處理。
CPU密集型任務(wù) 配置盡可能少的線程數(shù)量,如配置Ncpu+1個線程的線程池。
IO密集型任務(wù) 則由于需要等待IO操作,線程并不是一直在執(zhí)行任務(wù),則配置盡可能多的線程,如2*Ncpu。
混合型的任務(wù) 如果可以拆分,則將其拆分成一個CPU密集型任務(wù)和一個IO密集型任務(wù),只要這兩個任務(wù)執(zhí)行的時間相差不是太大,那么分解后執(zhí)行的吞吐率要高于串行執(zhí)行的吞吐率,如果這兩個任務(wù)執(zhí)行時間相差太大,則沒必要進行分解。
我們可以通過Runtime.getRuntime().availableProcessors()方法獲得當(dāng)前設(shè)備的CPU個數(shù)。
優(yōu)先級不同的任務(wù)可以使用優(yōu)先級隊列PriorityBlockingQueue來處理。它可以讓優(yōu)先級高的任務(wù)先得到執(zhí)行,需要注意的是如果一直有優(yōu)先級高的任務(wù)提交到隊列里,那么優(yōu)先級低的任務(wù)可能永遠(yuǎn)不能執(zhí)行。
執(zhí)行時間不同的任務(wù)可以交給不同規(guī)模的線程池來處理,或者也可以使用優(yōu)先級隊列,讓執(zhí)行時間短的任務(wù)先執(zhí)行。
依賴數(shù)據(jù)庫連接池的任務(wù),因為線程提交SQL后需要等待數(shù)據(jù)庫返回結(jié)果,如果等待的時間越長CPU空閑時間就越長,那么線程數(shù)應(yīng)該設(shè)置越大,這樣才能更好的利用CPU。
建議使用有界隊列,有界隊列能增加系統(tǒng)的穩(wěn)定性和預(yù)警能力,可以根據(jù)需要設(shè)大一點,比如幾千。
別人的例子:
有一次我們組使用的后臺任務(wù)線程池的隊列和線程池全滿了,不斷的拋出拋棄任務(wù)的異常,通過排查發(fā)現(xiàn)是數(shù)據(jù)庫出現(xiàn)了問題,導(dǎo)致執(zhí)行SQL變得非常緩慢,因為后臺任務(wù)線程池里的任務(wù)全是需要向數(shù)據(jù)庫查詢和插入數(shù)據(jù)的,所以導(dǎo)致線程池里的工作線程全部阻塞住,任務(wù)積壓在線程池里。如果當(dāng)時我們設(shè)置成無界隊列,線程池的隊列就會越來越多,有可能會撐滿內(nèi)存,導(dǎo)致整個系統(tǒng)不可用,而不只是后臺任務(wù)出現(xiàn)問題。當(dāng)然我們的系統(tǒng)所有的任務(wù)是用的多帶帶的服務(wù)器部署的,而我們使用不同規(guī)模的線程池跑不同類型的任務(wù),但是出現(xiàn)這樣問題時也會影響到其他任務(wù)。線程池的監(jiān)控
通過線程池提供的參數(shù)進行監(jiān)控。線程池里有一些屬性在監(jiān)控線程池的時候可以使用
--
taskCount:線程池需要執(zhí)行的任務(wù)數(shù)量。
completedTaskCount:線程池在運行過程中已完成的任務(wù)數(shù)量。小于或等于taskCount。
largestPoolSize:線程池曾經(jīng)創(chuàng)建過的最大線程數(shù)量。通過這個數(shù)據(jù)可以知道線程池是否滿過。如等于線程池的最大大小,則表示線程池曾經(jīng)滿了。
getPoolSize:線程池的線程數(shù)量。如果線程池不銷毀的話,池里的線程不會自動銷毀,所以這個大小只增不減。
getActiveCount:獲取活動的線程數(shù)。
通過擴展線程池進行監(jiān)控。通過繼承線程池并重寫線程池的beforeExecute,afterExecute,terminated方法,我們可以在任務(wù)執(zhí)行前,執(zhí)行后和線程池關(guān)閉前干一些事情。
如監(jiān)控任務(wù)的平均執(zhí)行時間,最大執(zhí)行時間和最小執(zhí)行時間等。這幾個方法在線程池里是空方法。如:
protected void beforeExecute(Thread t, Runnable r) { }
常用的幾種線程池什么是 Executor 框架 ? (面試題)
Executor框架在Java 5中被引入,Executor 框架是一個根據(jù)一組執(zhí)行策略調(diào)用、調(diào)度、執(zhí)行和控制的異步任務(wù)的框架。
Executor 框架包括:線程池,Executor,Executors,ExecutorService,CompletionService,F(xiàn)uture,Callable 等。
不過在java doc中,并不提倡我們直接使用ThreadPoolExecutor,而是使用Executors類中提供的幾個靜態(tài)方法來創(chuàng)建四種線程池:
注意在全新的阿里編程規(guī)約里面不推薦使用Executors提供的靜態(tài)方法創(chuàng)建線程。
newCachedThreadPool 是一個可根據(jù)需要創(chuàng)建新線程的線程池,創(chuàng)建一個可緩存線程池,如果線程池長度超過處理需要,可靈活回收空閑線程,若無可回收,則新建線程。。
ExecutorService cachedThreadPool = Executors.newCachedThreadPool(); cachedThreadPool.execute(new Runnable() { public void run() { System.out.println("runing....."); } }); }
--
newSingleThreadExecutor 創(chuàng)建是一個單線程池,也就是該線程池只有一個線程在工作,所有的任務(wù)是串行執(zhí)行的。如果這個唯一的線程因為異常結(jié)束,那么會有一個新的線程來替代它,此線程池保證所有任務(wù)的執(zhí)行順序按照任務(wù)的提交順序執(zhí)行。
ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();); singleThreadExecutor.execute(new Runnable() { public void run() { System.out.println("runing....."); } }); }
--
newFixedThreadPool 創(chuàng)建固定大小的線程池,每次提交一個任務(wù)就創(chuàng)建一個線程,直到線程達(dá)到線程池的最大大小。線程池的大小一旦達(dá)到最大值就會保持不變,如果某個線程因為執(zhí)行異常而結(jié)束,那么線程池會補充一個新線程。
定長線程池的大小最好根據(jù)系統(tǒng)資源進行設(shè)置。如Runtime.getRuntime().availableProcessors()
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3); fixedThreadPool .execute(new Runnable() { public void run() { System.out.println("runing....."); } }); }
--
newScheduledThreadPool 創(chuàng)建一個定長線程池,支持定時及周期性任務(wù)執(zhí)行。
ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5); scheduledThreadPool.schedule(new Runnable() { public void run() { System.out.println("runing....."); } }, 3, TimeUnit.SECONDS); // 表示延遲3秒執(zhí)行。 }使用線程池的風(fēng)險
雖然線程池是構(gòu)建多線程應(yīng)用程序的強大機制,但使用它并不是沒有風(fēng)險的。
用線程池構(gòu)建的應(yīng)用程序容易遭受任何其它多線程應(yīng)用程序容易遭受的所有并發(fā)風(fēng)險,諸如同步錯誤和死鎖,它還容易遭受特定于線程池的少數(shù)其它風(fēng)險,諸如與池有關(guān)的死鎖、資源不足和線程泄漏。
死鎖任何多線程應(yīng)用程序都有死鎖風(fēng)險。當(dāng)一組進程或線程中的每一個都在等待一個只有該組中另一個進程才能引起的事件時,我們就說這組進程或線程 死鎖了。
死鎖的最簡單情形是:線程 A 持有對象 X 的獨占鎖,并且在等待對象 Y 的鎖,而線程 B 持有對象 Y 的獨占鎖,卻在等待對象 X 的鎖。除非有某種方法來打破對鎖的等待(Java 鎖定不支持這種方法),否則死鎖的線程將永遠(yuǎn)等下去。
雖然任何多線程程序中都有死鎖的風(fēng)險,但線程池卻引入了另一種死鎖可能,在那種情況下,所有池線程都在執(zhí)行已阻塞的等待隊列中另一任務(wù)的執(zhí)行結(jié)果的任務(wù),但這一任務(wù)卻因為沒有未被占用的線程而不能運行。
當(dāng)線程池被用來實現(xiàn)涉及許多交互對象的模擬,被模擬的對象可以相互發(fā)送查詢,這些查詢接下來作為排隊的任務(wù)執(zhí)行,查詢對象又同步等待著響應(yīng)時,會發(fā)生這種情況。
線程池的一個優(yōu)點在于:相對于其它替代調(diào)度機制言,它們通常執(zhí)行得很好。但只有恰當(dāng)?shù)卣{(diào)整了線程池大小時才是這樣的。線程消耗包括內(nèi)存和其它系統(tǒng)資源在內(nèi)的大量資源。除了 Thread 對象所需的內(nèi)存之外,每個線程都需要兩個可能很大的執(zhí)行調(diào)用堆棧。除此以外,JVM 可能會為每個 Java 線程創(chuàng)建一個本機線程,這些本機線程將消耗額外的系統(tǒng)資源。最后,雖然線程之間切換的調(diào)度開銷很小,但如果有很多線程,環(huán)境切換也可能嚴(yán)重地影響程序的性能。
如果線程池太大,那么被那些線程消耗的資源可能嚴(yán)重地影響系統(tǒng)性能。在線程之間進行切換將會浪費時間,而且使用超出比您實際需要的線程可能會引起資源匱乏問題,因為池線程正在消耗一些資源,而這些資源可能會被其它任務(wù)更有效地利用。除了線程自身所使用的資源以外,服務(wù)請求時所做的工作可能需要其它資源,例如 JDBC 連接、套接字或文件。
這些也都是有限資源,有太多的并發(fā)請求也可能引起失效,例如不能分配 JDBC 連接。
線程池和其它排隊機制依靠使用 wait() 和 notify() 方法,這兩個方法都難于使用。如果編碼不正確,那么可能丟失通知,導(dǎo)致線程保持空閑狀態(tài),盡管隊列中有工作要處理。使用這些方法時,必須格外小心。而最好使用現(xiàn)有的、已經(jīng)知道能工作的實現(xiàn),例如 util.concurrent 包。
線程泄漏各種類型的線程池中一個嚴(yán)重的風(fēng)險是線程泄漏,當(dāng)從池中除去一個線程以執(zhí)行一項任務(wù),而在任務(wù)完成后該線程卻沒有返回池時,會發(fā)生這種情況。發(fā)生線程泄漏的一種情形出現(xiàn)在任務(wù)拋出一個 RuntimeException 或一個 Error 時。如果池類沒有捕捉到它們,那么線程只會退出而線程池的大小將會永久減少一個。當(dāng)這種情況發(fā)生的次數(shù)足夠多時,線程池最終就為空,而且系統(tǒng)將停止,因為沒有可用的線程來處理任務(wù)。
有些任務(wù)可能會永遠(yuǎn)等待某些資源或來自用戶的輸入,而這些資源又不能保證變得可用,用戶可能也已經(jīng)回家了,諸如此類的任務(wù)會永久停止,而這些停止的任務(wù)也會引起和線程泄漏同樣的問題。如果某個線程被這樣一個任務(wù)永久地消耗著,那么它實際上就被從池除去了。對于這樣的任務(wù),應(yīng)該要么只給予它們自己的線程,要么只讓它們等待有限的時間。
請求過載僅僅是請求就壓垮了服務(wù)器,這種情況是可能的。在這種情形下,我們可能不想將每個到來的請求都排隊到我們的工作隊列,因為排在隊列中等待執(zhí)行的任務(wù)可能會消耗太多的系統(tǒng)資源并引起資源缺乏。在這種情形下決定如何做取決于您自己;在某些情況下,您可以簡單地拋棄請求,依靠更高級別的協(xié)議稍后重試請求,您也可以用一個指出服務(wù)器暫時很忙的響應(yīng)來拒絕請求。
可選擇的阻塞隊列BlockingQueue詳解重復(fù)看一下新任務(wù)進入時線程池的執(zhí)行策略:
如果運行的線程少于corePoolSize,則 Executor始終首選添加新的線程,而不進行排隊。
如果運行的線程大于等于 corePoolSize,則 Executor始終首選將請求加入隊列,而不添加新的線程。
如果無法將請求加入隊列,則創(chuàng)建新的線程,除非創(chuàng)建此線程超出 maximumPoolSize,在這種情況下,任務(wù)將被拒絕。
主要有3種類型的BlockingQueue:
無界隊列隊列大小無限制,常用的為無界的LinkedBlockingQueue,將導(dǎo)致在所有 corePoolSize 線程都忙時新任務(wù)在隊列中等待。這樣,創(chuàng)建的線程就不會超過 corePoolSize。
應(yīng)用場景:當(dāng)每個任務(wù)完全獨立于其他任務(wù),即任務(wù)執(zhí)行互不影響時,適合于使用無界隊列。
例如,在 Web 頁服務(wù)器中。這種排隊可用于處理瞬態(tài)突發(fā)請求,當(dāng)命令以超過隊列所能處理的平均數(shù)連續(xù)到達(dá)時,此策略允許無界線程具有增長的可能性。
常用的有兩類,一類是遵循FIFO原則的隊列如ArrayBlockingQueue與有界的LinkedBlockingQueue,另一類是優(yōu)先級隊列如PriorityBlockingQueue。PriorityBlockingQueue中的優(yōu)先級由任務(wù)的Comparator決定。
使用有界隊列時隊列大小需和線程池大小互相配合,線程池較小有界隊列較大時可減少內(nèi)存消耗,降低cpu使用率和上下文切換,但是可能會限制系統(tǒng)吞吐量。
當(dāng)使用有限的 maximumPoolSizes 時,有界隊列(如 ArrayBlockingQueue)有助于防止資源耗盡,但是可能較難調(diào)整和控制。隊列大小和最大池大小可能需要相互折衷,使用大型隊列和小型池可以最大限度地降低 CPU 使用率、操作系統(tǒng)資源和上下文切換開銷,但是可能導(dǎo)致人工降低吞吐量。
如果任務(wù)頻繁阻塞(例如,如果它們是 I/O 邊界),則系統(tǒng)可能為超過您許可的更多線程安排時間。使用小型隊列通常要求較大的池大小,CPU 使用率較高,但是可能遇到不可接受的調(diào)度開銷,這樣也會降低吞吐量。
同步移交(直接提交) 如果不希望任務(wù)在隊列中等待而是希望將任務(wù)直接移交給工作線程,可使用SynchronousQueue作為等待隊列。SynchronousQueue不是一個真正的隊列,而是一種線程之間移交的機制。要將一個元素放入SynchronousQueue中,必須有另一個線程正在等待接收這個元素。只有在使用無界線程池或者有飽和策略時才建議使用該隊列。
工作隊列的默認(rèn)選項是 SynchronousQueue,此策略可以 避免在處理可能具有內(nèi)部依賴性的請求集時出現(xiàn)鎖。
該Queue本身的特性,在某次添加元素后必須等待其他線程取走后才能繼續(xù)添加。
可選擇的飽和策略RejectedExecutionHandler詳解JDK主要提供了4種飽和策略供選擇。4種策略都做為靜態(tài)內(nèi)部類在ThreadPoolExcutor中進行實現(xiàn)。
AbortPolicy中止策略public void rejectedExecution(Runnable r, ThreadPoolExecutor e) { throw new RejectedExecutionException("Task " + r.toString() + " rejected from " + e.toString()); }
使用該策略時在飽和時會拋出RejectedExecutionException(繼承自RuntimeException),調(diào)用者可捕獲該異常自行處理。
DiscardPolicy拋棄策略public void rejectedExecution(Runnable r, ThreadPoolExecutor e) { }
如代碼所示,不做任何處理直接拋棄任務(wù)
DiscardOldestPolicy拋棄舊任務(wù)策略public void rejectedExecution(Runnable r, ThreadPoolExecutor e) { if (!e.isShutdown()) { e.getQueue().poll(); e.execute(r); } }
如代碼,先將阻塞隊列中的頭元素出隊拋棄,再嘗試提交任務(wù)。如果此時阻塞隊列使用PriorityBlockingQueue優(yōu)先級隊列,將會導(dǎo)致優(yōu)先級最高的任務(wù)被拋棄,因此不建議將該種策略配合優(yōu)先級隊列使用。
CallerRunsPolicy調(diào)用者運行public void rejectedExecution(Runnable r, ThreadPoolExecutor e) { if (!e.isShutdown()) { r.run(); } }
既不拋棄任務(wù)也不拋出異常,直接運行任務(wù)的run方法,換言之將任務(wù)回退給調(diào)用者來直接運行。使用該策略時線程池飽和后將由調(diào)用線程池的主線程自己來執(zhí)行任務(wù),因此在執(zhí)行任務(wù)的這段時間里主線程無法再提交新任務(wù),從而使線程池中工作線程有時間將正在處理的任務(wù)處理完成。
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://m.hztianpu.com/yun/68819.html
Javascript只有六個假值(用在條件if的判斷) showImg(https://segmentfault.com/img/bVLiHL?w=424&h=346); 全等于 類型不同,返回false類型相同,則 showImg(https://segmentfault.com/img/bVLiHS?w=476&h=341); 等于 類型相同:同上=== 類型不同:嘗試類型轉(zhuǎn)換==【不是真值和...
摘要:命令規(guī)定的是對外的接口,必須與模塊內(nèi)部的變量建立一一對應(yīng)關(guān)系。意思是導(dǎo)出的不是一個具體的數(shù)值,而是一個對象命令接受一對大括號,里面指定要從其他模塊導(dǎo)入的變量名。大括號里面的變量名,必須與被導(dǎo)入模塊對外接口的名稱相同。 一、module.exports與exports nodeJS采用commonJs規(guī)范,當(dāng)前文件是一個模塊(module)私有域,通過exports屬性導(dǎo)出,通過re...
摘要:前言是現(xiàn)在幾乎每個項目中必備的一個東西,但是其工作原理避不開對的解析在生成的過程,有引擎,早期了項目,了解這個之前我們先來看看這種引擎解析出來是什么東西。 前言 babel是現(xiàn)在幾乎每個項目中必備的一個東西,但是其工作原理避不開對js的解析在生成的過程,babel有引擎babylon,早期fork了項目acron,了解這個之前我們先來看看這種引擎解析出來是什么東西。不光是babel還有...
摘要:很多小白在看過很多教程之后仍然在敲代碼的時候不清楚應(yīng)該以什么樣的步驟進行,那么這篇文章就一步一步分解整個過程,慢動作回放讓大家看的清清楚楚明明白白。另外,中視圖部分最好單獨出來,放在新建一個文件夾目錄下,并被名為引用,把其他邏輯部分放后者。 whay write this: 很多小白在看過很多教程之后仍然在敲代碼的時候不清楚應(yīng)該以什么樣的步驟進行,那么這篇文章就一步一步分解整個過程,慢...
摘要:今天,云服務(wù)器網(wǎng)來詳細(xì)說下云服務(wù)器公網(wǎng)帶寬上行帶寬下行帶寬出網(wǎng)流量和入網(wǎng)流量,本文所述的帶寬是指公網(wǎng)帶寬,內(nèi)網(wǎng)帶寬是免費的。什么是服務(wù)器上行帶寬?花錢購買的公網(wǎng)帶寬是上行還是下行?用戶花錢購買公網(wǎng)帶寬是上行帶寬,下行帶寬不收費,上行帶寬是指云服務(wù)器出網(wǎng)帶寬,流量流出云服務(wù)器的方向;那么,如果從云服務(wù)器下載軟件,這就是下行帶寬,也是流量流入云服務(wù)器方向的帶寬。今天,云服務(wù)器網(wǎng)(yuntue.c...
閱讀 3012·2021-11-22 09:34
閱讀 1284·2021-11-19 09:40
閱讀 3423·2021-10-14 09:43
閱讀 3638·2021-09-23 11:22
閱讀 1673·2021-08-31 09:39
閱讀 979·2019-08-30 15:55
閱讀 1489·2019-08-30 15:54
閱讀 920·2019-08-30 15:53