多線程之線程池(上)
前言
最近一段時(shí)間,我們一直都在分享多線程相關(guān)知識(shí),也一直有用線程池,但是一直沒有介紹線程池相關(guān)知識(shí),所以今天我們就先來看下線程池相關(guān)的知識(shí)點(diǎn)。
線程池
ThreadPool,線程池,顧名思義就是存放線程的池子,也是jdk1.5引入的。對(duì)我們而言,它的最主要優(yōu)勢就是簡化了線程啟動(dòng)流程,讓我們可以更方便地使用多線程,再也不用手動(dòng)start線程,直接通過線程池提交我們的任務(wù)即可,而且合理使用線程池至于可以帶來以下幾個(gè)好處:
降低資源消耗:復(fù)用線程,降低創(chuàng)建和銷毀線程帶來的資源消耗
提高響應(yīng)速度:使用線程池,省去了線程創(chuàng)建和初始化過程,所以任務(wù)可以更快執(zhí)行
提高線程的可管理性:可以直接通過線程池管理、監(jiān)控、調(diào)度線程,線程管理更方便
常用線程池
常用的線程池有SingleThreadExecutor、CachedThreadPool、ScheduledThreadPool、FixedThreadPool,他們分別是單線程調(diào)度器,緩存線程池,定時(shí)任務(wù)線程池和固定線程池,他們都可以通過Executors創(chuàng)建,調(diào)用對(duì)應(yīng)的靜態(tài)方法即可,由于這一塊的內(nèi)容比較多,所以今天就簡單提一下,后面專門講一次。

自定義線程池
我們今天著重講下自定義線程池,自定義線程池也很簡單,直接new ThreadPoolExecutor(),然后傳入對(duì)應(yīng)的參數(shù)即可,大家可以看下下面的示例:
int corePoolSize = 10;
int maximumPoolSize = 20;
long keepAliveTime = 1000;
TimeUnit unit = TimeUnit.MICROSECONDS;
BlockingDeque<Runnable> workQueue = new LinkedBlockingDeque<>();
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
for (int i = 0; i < 50; i++) {
threadPoolExecutor.execute(() -> {
String name = Thread.currentThread().getName();
System.out.println("hello threadPool: "+ name);
});
}
threadPoolExecutor.shutdown();
ThreadPoolExecutor有三個(gè)構(gòu)造方法,至少需要三個(gè)參數(shù)

其中第一個(gè)參數(shù)是線程池的基本大小,當(dāng)你提交一個(gè)任務(wù)到線程池時(shí),線程池會(huì)創(chuàng)建一個(gè)線程來執(zhí)行任務(wù),即使有空閑的線程存在,線程池依然會(huì)啟動(dòng)一個(gè)新線程來執(zhí)行當(dāng)前任務(wù),直到線程池中的線程數(shù)達(dá)到線程基本大小(corePoolSize);
第二個(gè)參數(shù)是線程池允許創(chuàng)建的最大線程數(shù)。如果工作隊(duì)列(第三個(gè)參數(shù))滿了,且創(chuàng)建線程數(shù)已達(dá)到線程池基本大小,則線程池會(huì)繼續(xù)創(chuàng)建新的線程來執(zhí)行任務(wù)。如果你指定的工作隊(duì)列是無界的,那這個(gè)參數(shù)也就失效了。
第三個(gè)參數(shù)就是線程池工作隊(duì)列,就是當(dāng)你需要執(zhí)行的任務(wù)超過線程池基本大小的時(shí)候,會(huì)把超出部分放進(jìn)工作隊(duì)列,等待線程池基本線程資源釋放。
下面我們分別驗(yàn)證以上三點(diǎn),運(yùn)行上面的示例代碼:

在第一次循環(huán)的時(shí)候(i=0),我們發(fā)現(xiàn)線程池的size是0,活動(dòng)線程數(shù)也是0,任務(wù)隊(duì)列也是0,完成任務(wù)數(shù)也是0,這也說明線程池在最開始的時(shí)候是沒有創(chuàng)建線程的;
然后我們讓他循環(huán)到第9次(i=8),這時(shí)候線程池已經(jīng)被初始化,有8個(gè)線程(由于斷點(diǎn)的原因,第9個(gè)線程尚未被創(chuàng)建),活動(dòng)線程數(shù)5,完成執(zhí)行的任務(wù)數(shù)3,任務(wù)隊(duì)列還是0,說明確實(shí)在未達(dá)到線程池基本大小時(shí),會(huì)不斷創(chuàng)建新的線程;

我們繼續(xù)執(zhí)行,讓他循環(huán)到第15次(i=14),可結(jié)果似乎和我們預(yù)期不一樣,按照預(yù)期,線程池的size應(yīng)該是10,活動(dòng)線程數(shù)也是10,任務(wù)隊(duì)列也是4,完成任務(wù)數(shù)可能不確定,所以這里肯定不能通過debug的方式來看了,因?yàn)?code style="overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(255, 100, 65);">debug停頓之后好多線程資源已經(jīng)被釋放,任務(wù)根本就不會(huì)堆積,所以任務(wù)隊(duì)列就不會(huì)有數(shù)據(jù):

所以這里我在線程池啟動(dòng)任務(wù)前加一行打印,打印線程池?cái)?shù)據(jù)

然后在運(yùn)行,就可以拿到運(yùn)行數(shù)據(jù):

這樣的數(shù)據(jù)才是真實(shí)的,因?yàn)閷?shí)際運(yùn)行的時(shí)候,線程啟動(dòng)是非常快的,所以執(zhí)行完成的任務(wù)數(shù)應(yīng)該是0,等待任務(wù)數(shù)是4。

我們前面設(shè)定的最大線程數(shù)是20,但是翻看運(yùn)行記錄,我發(fā)現(xiàn)線程池的大小始終是10,說明只要不打到任務(wù)隊(duì)列的上限,并不會(huì)創(chuàng)建新的線程,這里我們把循環(huán)次數(shù)改為60,然后運(yùn)行下:

但是依然沒有創(chuàng)建新的線程,因?yàn)檫€是沒有達(dá)到任務(wù)隊(duì)列上限,我們把循環(huán)次數(shù)再調(diào)大一點(diǎn),調(diào)到70:

現(xiàn)在線程池的大小就變成了19,活動(dòng)線程數(shù)19,但是這時(shí)候如果你繼續(xù)調(diào)大循環(huán)次數(shù),線程池就會(huì)報(bào)錯(cuò)了:

這個(gè)錯(cuò)誤的原因就是線程池資源已經(jīng)耗盡了,無法再接收新的任務(wù)了,這也就是說線程池能夠處理的最大任務(wù)數(shù)是corePoolSize + maximumPoolSize + workQueue.size() ,當(dāng)然,如果你的workQueue不設(shè)定大小,那永遠(yuǎn)都不會(huì)報(bào)這個(gè)錯(cuò)誤,當(dāng)然maximumPoolSize也就無效了。
總結(jié)
原本打算線程池一次分享完的,但是實(shí)際分享過程中發(fā)現(xiàn)內(nèi)容太多了(已經(jīng)一千五百字了),所以今天就先到這里,明天再繼續(xù)分享線程池其他內(nèi)容。總的來說,今天的內(nèi)容已經(jīng)說明白了線程池很多基礎(chǔ)的知識(shí)點(diǎn)(反正我自己覺得我都有好多收獲),算是干貨滿滿吧,你如果掌握了這些知識(shí)點(diǎn),至少在使用線程池的過程中會(huì)少踩好多坑。好了,今天就先說這么多吧!
- END -