线程池最佳线程数量到底要如何配置?
一、前言
對(duì)于從事后端開(kāi)發(fā)的同學(xué)來(lái)說(shuō),線程是必須要使用了,因?yàn)槭褂盟梢蕴嵘到y(tǒng)的性能。但是,創(chuàng)建線程和銷毀線程都是比較耗時(shí)的操作,頻繁的創(chuàng)建和銷毀線程會(huì)浪費(fèi)很多CPU的資源。
此外,如果每個(gè)任務(wù)都創(chuàng)建一個(gè)線程去處理,這樣線程會(huì)越來(lái)越多。我們知道每個(gè)線程默認(rèn)情況下占1M的內(nèi)存空間,如果線程非常多,內(nèi)存資源將會(huì)被耗盡。這時(shí),我們需要線程池去管理線程,不會(huì)出現(xiàn)內(nèi)存資源被耗盡的情況,也不會(huì)出現(xiàn)頻繁創(chuàng)建和銷毀線程的情況,因?yàn)樗鼉?nèi)部是可以復(fù)用線程的。
?
二、從實(shí)戰(zhàn)開(kāi)始
在介紹線程池之前,讓我們先看個(gè)例子。
public class MyCallable implements Callable<String> { @Override public String call() throws Exception { System.out.println("MyCallable call");return "success"; } public static void main(String[] args) {ExecutorService threadPool = Executors.newSingleThreadExecutor();try {Future<String> future = threadPool.submit(new MyCallable()); System.out.println(future.get()); } catch (Exception e) { System.out.println(e); } finally { threadPool.shutdown(); }} }這個(gè)類的功能就是使用Executors類的newSingleThreadExecutor方法創(chuàng)建了的一個(gè)單線程池,他里面會(huì)執(zhí)行Callable線程任務(wù)。
三、創(chuàng)建線程池的方法
我們仔細(xì)看看Executors類,會(huì)發(fā)現(xiàn)它里面給我們封裝了不少創(chuàng)建線程池的靜態(tài)方法,如下圖所示:
其實(shí),我們總結(jié)一下其實(shí)只有6種:
1.newCachedThreadPool可緩沖線程池
它的核心線程數(shù)是0,最大線程數(shù)是integer的最大值,每隔60秒回收一次空閑線程,使用SynchronousQueue隊(duì)列。SynchronousQueue隊(duì)列比較特殊,內(nèi)部只包含一個(gè)元素,插入元素到隊(duì)列的線程被阻塞,直到另一個(gè)線程從隊(duì)列中獲取了隊(duì)列中存儲(chǔ)的元素。同樣,如果線程嘗試獲取元素并且當(dāng)前不存在任何元素,則該線程將被阻塞,直到線程將元素插入隊(duì)列。
2.newFixedThreadPool固定大小線程池
它的核心線程數(shù) 和 最大線程數(shù)是一樣,都是nThreads變量的值,該變量由用戶自己決定,所以說(shuō)是固定大小線程池。此外,它的每隔0毫秒回收一次線程,換句話說(shuō)就是不回收線程,因?yàn)樗暮诵木€程數(shù) 和 最大線程數(shù)是一樣,回收了沒(méi)有任何意義。此外,使用了LinkedBlockingQueue隊(duì)列,該隊(duì)列其實(shí)是有界隊(duì)列,很多人誤解了,只是它的初始大小比較大是integer的最大值。
3.newScheduledThreadPool定時(shí)任務(wù)線程池
它的核心線程數(shù)是corePoolSize變量,需要用戶自己決定,最大線程數(shù)是integer的最大值,同樣,它的每隔0毫秒回收一次線程,換句話說(shuō)就是不回收線程。使用了DelayedWorkQueue隊(duì)列,該隊(duì)列具有延時(shí)的功能。
4.newSingleThreadExecutor單個(gè)線程池
其實(shí),跟上面的newFixedThreadPool是一樣的,稍微有一點(diǎn)區(qū)別是核心線程數(shù) 和 最大線程數(shù) 都是1,這就是為什么說(shuō)它是單線程池的原因。
5.newSingleThreadScheduledExecutor單線程定時(shí)任務(wù)線程池
該線程池是對(duì)上面介紹過(guò)的ScheduledThreadPoolExecutor定時(shí)任務(wù)線程池的簡(jiǎn)單封裝,核心線程數(shù)固定是1,其他的功能一模一樣。
6.newWorkStealingPool竊取線程池
它是JDK1.8增加的新線程池,跟其他的實(shí)現(xiàn)方式都不一樣,它底層是通過(guò)ForkJoinPool類來(lái)實(shí)現(xiàn)的。會(huì)創(chuàng)建一個(gè)含有足夠多線程的線程池,來(lái)維持相應(yīng)的并行級(jí)別,它會(huì)通過(guò)工作竊取的方式,使得多核的 CPU 不會(huì)閑置,總會(huì)有活著的線程讓 CPU 去運(yùn)行。
講了這么多,具體要怎么用呢?
其實(shí)newFixedThreadPool、newCachedThreadPool、newSingleThreadExecutor 和 newWorkStealingPool方法創(chuàng)建和使用線程池的方法是一樣的。這四個(gè)方法創(chuàng)建線程池返回值是ExecutorService,通過(guò)它的execute方法執(zhí)行線程。
public class MyWorker implements Runnable {@Override public void run() {System.out.println("MyWorker run");}public static void main(String[] args) {ExecutorService threadPool = Executors.newFixedThreadPool(8); try {threadPool.execute(new MyWorker());} catch (Exception e) {System.out.println(e);} finally {threadPool.shutdown();}} }newScheduledThreadPool 和 newSingleThreadScheduledExecutor 方法創(chuàng)建和使用線程池的方法也是一樣的
以上兩個(gè)方法創(chuàng)建的線程池返回值是ScheduledExecutorService,通過(guò)它的schedule提交線程,并且可以配置延遲執(zhí)行的時(shí)間。
?
四、自定義線程池
Executors類有這么多方法可以創(chuàng)建線程池,但是阿里巴巴開(kāi)發(fā)規(guī)范中卻明確規(guī)定不要使用Executors類創(chuàng)建線程池,這是為什么呢?
newCachedThreadPool可緩沖線程池,它的最大線程數(shù)是integer的最大值,意味著使用它創(chuàng)建的線程池,可以創(chuàng)建非常多的線程,我們都知道一個(gè)線程默認(rèn)情況下占用內(nèi)存1M,如果創(chuàng)建的線程太多,占用內(nèi)存太大,最后肯定會(huì)出現(xiàn)內(nèi)存溢出的問(wèn)題。
newFixedThreadPool和newSingleThreadExecutor在這里都稱為固定大小線程池,它的隊(duì)列使用的LinkedBlockingQueue,我們都知道這個(gè)隊(duì)列默認(rèn)大小是integer的最大值,意味著可以往該隊(duì)列中加非常多的任務(wù),每個(gè)任務(wù)也是要內(nèi)存空間的,如果任務(wù)太多,最后肯定也會(huì)出現(xiàn)內(nèi)存溢出的問(wèn)題。
阿里建議使用ThreadPoolExecutor類創(chuàng)建線程池,其實(shí)從剛剛看到的Executors類創(chuàng)建線程池的newFixedThreadPool等方法可以看出,它也是使用ThreadPoolExecutor類創(chuàng)建線程池的。
從上圖可以看出ThreadPoolExecutor類的構(gòu)造方法有4個(gè),里面包含了很多參數(shù),讓我們先一起認(rèn)識(shí)一下:
corePoolSize:核心線程數(shù) maximumPoolSize:最大線程數(shù) keepAliveTime:空閑線程回收時(shí)間間隔 unit:空閑線程回收時(shí)間間隔單位 workQueue:提交任務(wù)的隊(duì)列,當(dāng)線程數(shù)量超過(guò)核心線程數(shù)時(shí),可以將任務(wù)提交到任務(wù)隊(duì)列中。比較常用的有:ArrayBlockingQueue; LinkedBlockingQueue; SynchronousQueue; threadFactory:線程工廠,可以自定義線程的一些屬性,比如:名稱或者守護(hù)線程等 handler:表示當(dāng)拒絕處理任務(wù)時(shí)的策略 ThreadPoolExecutor.AbortPolicy:丟棄任務(wù)并拋出RejectedExecutionException異常。 ThreadPoolExecutor.DiscardPolicy:也是丟棄任務(wù),但是不拋出異常。 ThreadPoolExecutor.DiscardOldestPolicy:丟棄隊(duì)列最前面的任務(wù),然后重新嘗試執(zhí)行任務(wù)(重復(fù)此過(guò)程) ThreadPoolExecutor.CallerRunsPolicy:由調(diào)用線程處理該任務(wù)我們根據(jù)上面的內(nèi)容自定義一個(gè)線程池:
從上面可以看到,我們使用ThreadPoolExecutor類自定義了一個(gè)線程池,它的核心線程數(shù)是8,最大線程數(shù)是 10,空閑線程回收時(shí)間是30,單位是秒,存放任務(wù)的隊(duì)列用的ArrayBlockingQueue,而隊(duì)列滿的處理策略用的AbortPolicy。使用這個(gè)隊(duì)列,基本可以保持線程在系統(tǒng)的可控范圍之內(nèi),不會(huì)出現(xiàn)內(nèi)存溢出的問(wèn)題。但是也不是絕對(duì)的,只是出現(xiàn)內(nèi)存溢出的概率比較小。
當(dāng)然,阿里巴巴開(kāi)發(fā)規(guī)范建議不使用Executors類創(chuàng)建線程池,并不表示它完全沒(méi)用,在一些低并發(fā)的業(yè)務(wù)場(chǎng)景照樣可以使用。
五、最佳線程數(shù)
在使用線程池時(shí),很多同學(xué)都有這樣的疑問(wèn),不知道如何配置線程數(shù)量,今天我們一起探討一下這個(gè)問(wèn)題。
1.經(jīng)驗(yàn)值
配置線程數(shù)量之前,首先要看任務(wù)的類型是 IO密集型,還是CPU密集型?
什么是IO密集型?
比如:頻繁讀取磁盤上的數(shù)據(jù),或者需要通過(guò)網(wǎng)絡(luò)遠(yuǎn)程調(diào)用接口。
什么是CPU密集型?
比如:非常復(fù)雜的調(diào)用,循環(huán)次數(shù)很多,或者遞歸調(diào)用層次很深等。
IO密集型配置線程數(shù)經(jīng)驗(yàn)值是:2N,其中N代表CPU核數(shù)。
CPU密集型配置線程數(shù)經(jīng)驗(yàn)值是:N + 1,其中N代表CPU核數(shù)。
如果獲取N的值?
int availableProcessors = Runtime.getRuntime().availableProcessors();那么問(wèn)題來(lái)了,混合型(既包含IO密集型,又包含CPU密集型)的如何配置線程數(shù)?
混合型如果IO密集型,和CPU密集型的執(zhí)行時(shí)間相差不太大,可以拆分開(kāi),以便于更好配置。如果執(zhí)行時(shí)間相差太大,優(yōu)化的意義不大,比如IO密集型耗時(shí)60s,CPU密集型耗時(shí)1s。
2.最佳線程數(shù)目算法
除了上面介紹是經(jīng)驗(yàn)值之外,其實(shí)還提供了計(jì)算公式:
最佳線程數(shù)目 = ((線程等待時(shí)間+線程CPU時(shí)間)/線程CPU時(shí)間 )* CPU數(shù)目很顯然線程等待時(shí)間所占比例越高,需要越多線程。線程CPU時(shí)間所占比例越高,需要越少線程。
雖說(shuō)最佳線程數(shù)目算法更準(zhǔn)確,但是線程等待時(shí)間和線程CPU時(shí)間不好測(cè)量,實(shí)際情況使用得比較少,一般用經(jīng)驗(yàn)值就差不多了。再配合系統(tǒng)壓測(cè),基本可以確定最適合的線程數(shù)。
有道無(wú)術(shù),術(shù)可成;有術(shù)無(wú)道,止于術(shù)
歡迎大家關(guān)注Java之道公眾號(hào)
好文章,我在看??
總結(jié)
以上是生活随笔為你收集整理的线程池最佳线程数量到底要如何配置?的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: Python基础知识一
- 下一篇: 字符与字符串3——char 的大小