Java中的線程池是運(yùn)用場景最多的并發(fā)框架定欧,幾乎所有需要異步或并發(fā)執(zhí)行任務(wù)的程序都可以使用線程池。在開發(fā)過程中怒竿,合理地使用線程池能夠帶來3個(gè)好處。
第一:降低資源消耗扩氢。通過重復(fù)利用已創(chuàng)建的線程降低線程創(chuàng)建和銷毀造成的消耗耕驰。
第二:提高響應(yīng)速度。當(dāng)任務(wù)到達(dá)時(shí)录豺,任務(wù)可以不需要等到線程創(chuàng)建就能立即執(zhí)行朦肘。
第三:提高線程的可管理性。線程是稀缺資源双饥,如果無限制地創(chuàng)建媒抠,不僅會(huì)消耗系統(tǒng)資源,還會(huì)降低系統(tǒng)的穩(wěn)定性咏花,使用線程池可以進(jìn)行統(tǒng)一分配趴生、調(diào)優(yōu)和監(jiān)控阀趴。但是,要做到合理利用線程池苍匆,必須對(duì)其實(shí)現(xiàn)原理了如指掌刘急。
線程池的實(shí)現(xiàn)原理
當(dāng)向線程池提交一個(gè)任務(wù)之后,線程池是如何處理這個(gè)任務(wù)的呢浸踩?來看一下線程池的主要處理流程叔汁,處理流程圖如圖所示。
從圖中可以看出检碗,當(dāng)提交一個(gè)新任務(wù)到線程池時(shí)据块,線程池的處理流程如下。
1)線程池判斷核心線程池里的線程是否都在執(zhí)行任務(wù)折剃。如果不是另假,則創(chuàng)建一個(gè)新的工作線程來執(zhí)行任務(wù)。如果核心線程池里的線程都在執(zhí)行任務(wù)微驶,則進(jìn)入下個(gè)流程浪谴。
2)線程池判斷工作隊(duì)列是否已經(jīng)滿。如果工作隊(duì)列沒有滿因苹,則將新提交的任務(wù)存儲(chǔ)在這個(gè)工作隊(duì)列里苟耻。如果工作隊(duì)列滿了,則進(jìn)入下個(gè)流程扶檐。
3)線程池判斷線程池的線程是否都處于工作狀態(tài)凶杖。如果沒有,則創(chuàng)建一個(gè)新的工作線程來執(zhí)行任務(wù)款筑。如果已經(jīng)滿了智蝠,則交給飽和策略來處理這個(gè)任務(wù)。
ThreadPoolExecutor執(zhí)行execute()方法的示意圖奈梳,如圖所示杈湾。
execute方法分下面4種情況。
1)如果當(dāng)前運(yùn)行的線程少于corePoolSize攘须,則創(chuàng)建新線程來執(zhí)行任務(wù)(注意漆撞,執(zhí)行這一步驟需要獲取全局鎖)。
2)如果運(yùn)行的線程等于或多于corePoolSize于宙,則將任務(wù)加入BlockingQueue浮驳。?
3)如果無法將任務(wù)加入BlockingQueue(隊(duì)列已滿),則創(chuàng)建新的線程來處理任務(wù)(注意捞魁,執(zhí)行這一步驟需要獲取全局鎖)至会。
4)如果創(chuàng)建新線程將使當(dāng)前運(yùn)行的線程超出maximumPoolSize,任務(wù)將被拒絕谱俭,并調(diào)用?RejectedExecutionHandler.rejectedExecution()方法奉件。
ThreadPoolExecutor采取上述步驟的總體設(shè)計(jì)思路宵蛀,是為了在執(zhí)行execute()方法時(shí),盡可能地避免獲取全局鎖(那將會(huì)是一個(gè)嚴(yán)重的可伸縮瓶頸)瓶蚂。在ThreadPoolExecutor完成預(yù)熱之后(當(dāng)前運(yùn)行的線程數(shù)大于等于corePoolSize)糖埋,幾乎所有的execute()方法調(diào)用都是執(zhí)行步驟2,而步驟2不需要獲取全局鎖窃这。
源碼分析:上面的流程分析讓我們很直觀地了解了線程池的工作原理瞳别,讓我們?cè)偻ㄟ^源代碼來看看是如何實(shí)現(xiàn)的,線程池執(zhí)行任務(wù)的方法如下杭攻。
工作線程:線程池創(chuàng)建線程時(shí)祟敛,會(huì)將線程封裝成工作線程Worker,Worker在執(zhí)行完任務(wù)后兆解,還會(huì)循環(huán)獲取工作隊(duì)列里的任務(wù)來執(zhí)行馆铁。我們可以從Worker類的run()方法里看到這點(diǎn)。
ThreadPoolExecutor中線程執(zhí)行任務(wù)的示意圖如圖所示锅睛。
線程池中的線程執(zhí)行任務(wù)分兩種情況埠巨,如下。
1)在execute()方法中創(chuàng)建一個(gè)線程時(shí)现拒,會(huì)讓這個(gè)線程執(zhí)行當(dāng)前任務(wù)辣垒。
2)這個(gè)線程執(zhí)行完上圖中1的任務(wù)后,會(huì)反復(fù)從BlockingQueue獲取任務(wù)來執(zhí)行印蔬。
線程池的創(chuàng)建
我們可以通過ThreadPoolExecutor來創(chuàng)建一個(gè)線程池勋桶。
new ThreadPoolExecutor(corePoolSize,maximumPoolSize,keepAliveTime,milliseconds,runnableTaskQueue,?handler);
創(chuàng)建一個(gè)線程池時(shí)需要輸入幾個(gè)參數(shù),如下侥猬。
1)corePoolSize(線程池的基本大欣浴):當(dāng)提交一個(gè)任務(wù)到線程池時(shí),線程池會(huì)創(chuàng)建一個(gè)線程來執(zhí)行任務(wù)退唠,即使其他空閑的基本線程能夠執(zhí)行新任務(wù)也會(huì)創(chuàng)建線程鹃锈,等到需要執(zhí)行的任務(wù)數(shù)大于線程池基本大小時(shí)就不再創(chuàng)建。如果調(diào)用了線程池的prestartAllCoreThreads()方法瞧预,線程池會(huì)提前創(chuàng)建并啟動(dòng)所有基本線程仪召。
2)runnableTaskQueue(任務(wù)隊(duì)列):用于保存等待執(zhí)行的任務(wù)的阻塞隊(duì)列∷伤猓可以選擇以下幾個(gè)阻塞隊(duì)列。
·ArrayBlockingQueue:是一個(gè)基于數(shù)組結(jié)構(gòu)的有界阻塞隊(duì)列已旧,此隊(duì)列按FIFO(先進(jìn)先出)原則對(duì)元素進(jìn)行排序秸苗。
·LinkedBlockingQueue:一個(gè)基于鏈表結(jié)構(gòu)的阻塞隊(duì)列,此隊(duì)列按FIFO排序元素运褪,吞吐量通常要高于ArrayBlockingQueue惊楼。靜態(tài)工廠方法Executors.newFixedThreadPool()使用了這個(gè)隊(duì)列玖瘸。
·SynchronousQueue:一個(gè)不存儲(chǔ)元素的阻塞隊(duì)列。每個(gè)插入操作必須等到另一個(gè)線程調(diào)用?移除操作檀咙,否則插入操作一直處于阻塞狀態(tài)雅倒,吞吐量通常要高于LinkedBlockingQueue,靜態(tài)工廠方法Executors.newCachedThreadPool使用了這個(gè)隊(duì)列弧可。
·PriorityBlockingQueue:一個(gè)具有優(yōu)先級(jí)的無限阻塞隊(duì)列蔑匣。
3)maximumPoolSize(線程池最大數(shù)量):線程池允許創(chuàng)建的最大線程數(shù)。如果隊(duì)列滿了棕诵,并且已創(chuàng)建的線程數(shù)小于最大線程數(shù)裁良,則線程池會(huì)再創(chuàng)建新的線程執(zhí)行任務(wù)。值得注意的是校套,如果使用了無界的任務(wù)隊(duì)列這個(gè)參數(shù)就沒什么效果价脾。
4)ThreadFactory:用于設(shè)置創(chuàng)建線程的工廠,可以通過線程工廠給每個(gè)創(chuàng)建出來的線程設(shè)?置更有意義的名字笛匙。使用開源框架guava提供的ThreadFactoryBuilder可以快速給線程池里的線?程設(shè)置有意義的名字侨把,代碼如下。
new??ThreadFactoryBuilder().setNameFormat("XX-task-%d").build();
5)RejectedExecutionHandler(飽和策略):當(dāng)隊(duì)列和線程池都滿了妹孙,說明線程池處于飽和狀態(tài)秋柄,那么必須采取一種策略處理提交的新任務(wù)。這個(gè)策略默認(rèn)情況下是AbortPolicy涕蜂,表示無法處理新任務(wù)時(shí)拋出異常华匾。在JDK?1.5中Java線程池框架提供了以下4種策略。
·AbortPolicy:直接拋出異常机隙。
·CallerRunsPolicy:只用調(diào)用者所在線程來運(yùn)行任務(wù)蜘拉。
·DiscardOldestPolicy:丟棄隊(duì)列里最近的一個(gè)任務(wù),并執(zhí)行當(dāng)前任務(wù)有鹿。
·DiscardPolicy:不處理旭旭,丟棄掉。?當(dāng)然葱跋,也可以根據(jù)應(yīng)用場景需要來實(shí)現(xiàn)RejectedExecutionHandler接口自定義策略持寄。如記錄日志或持久化存儲(chǔ)不能處理的任務(wù)。
6)AliveTime(線程活動(dòng)保持時(shí)間):線程池的工作線程空閑后娱俺,保持存活的時(shí)間辫继。所以郊愧,?如果任務(wù)很多,并且每個(gè)任務(wù)執(zhí)行的時(shí)間比較短,可以調(diào)大時(shí)間纪隙,提高線程的利用率摘能。
7)TimeUnit(線程活動(dòng)保持時(shí)間的單位):可選的單位有天(DAYS)、小時(shí)(HOURS)、分鐘(MINUTES)怜姿、毫秒(MILLISECONDS)、微秒(MICROSECONDS疼燥,千分之一毫秒)和納秒(NANOSECONDS)沧卢,千分之一微秒。
向線程池提交任務(wù)
可以使用兩個(gè)方法向線程池提交任務(wù)醉者,分別為execute()和submit()方法但狭。
execute()方法用于提交不需要返回值的任務(wù),所以無法判斷任務(wù)是否被線程池執(zhí)行成功湃交。?通過以下代碼可知execute()方法輸入的任務(wù)是一個(gè)Runnable類的實(shí)例熟空。
submit()方法用于提交需要返回值的任務(wù)。線程池會(huì)返回一個(gè)future類型的對(duì)象搞莺,通過這個(gè)?future對(duì)象可以判斷任務(wù)是否執(zhí)行成功息罗,并且可以通過future的get()方法來獲取返回值,get()方法會(huì)阻塞當(dāng)前線程直到任務(wù)完成才沧,而使用get(long?timeout迈喉,TimeUnit????unit)方法則會(huì)阻塞當(dāng)前線程一段時(shí)間后立即返回,這時(shí)候有可能任務(wù)沒有執(zhí)行完温圆。
關(guān)閉線程池
可以通過調(diào)用線程池的shutdown或shutdownNow方法來關(guān)閉線程池挨摸。它們的原理是遍歷線?程池中的工作線程,然后逐個(gè)調(diào)用線程的interrupt方法來中斷線程岁歉,所以無法響應(yīng)中斷的任務(wù)可能永遠(yuǎn)無法終止得运。但是它們存在一定的區(qū)別,shutdownNow首先將線程池的狀態(tài)設(shè)置成?STOP锅移,然后嘗試停止所有的正在執(zhí)行或暫停任務(wù)的線程熔掺,并返回等待執(zhí)行任務(wù)的列表,而?shutdown只是將線程池的狀態(tài)設(shè)置成SHUTDOWN狀態(tài)非剃,然后中斷所有沒有正在執(zhí)行任務(wù)的線?程置逻。
只要調(diào)用了這兩個(gè)關(guān)閉方法中的任意一個(gè),isShutdown方法就會(huì)返回true备绽。當(dāng)所有的任務(wù)?都已關(guān)閉后券坞,才表示線程池關(guān)閉成功,這時(shí)調(diào)用isTerminaed方法會(huì)返回true肺素。至于應(yīng)該調(diào)用哪一種方法來關(guān)閉線程池恨锚,應(yīng)該由提交到線程池的任務(wù)特性決定,通常調(diào)用shutdown方法來關(guān)閉線程池倍靡,如果任務(wù)不一定要執(zhí)行完眠冈,則可以調(diào)用shutdownNow方法。