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