一区二区三区日韩精品-日韩经典一区二区三区-五月激情综合丁香婷婷-欧美精品中文字幕专区

分享

更好的使用JAVA線程池

 然并卵書(shū)屋 2017-04-01

這篇文章分別從線程池大小參數(shù)的設(shè)置、工作線程的創(chuàng)建、空閑線程的回收、阻塞隊(duì)列的使用、任務(wù)拒絕策略、線程池Hook等方面來(lái)了解線程池的使用,其中涉及到一些細(xì)節(jié)包括不同參數(shù)、不同隊(duì)列、不同拒絕策略的選擇、產(chǎn)生的影響和行為、為更好的使用線程池奠定知識(shí)基礎(chǔ),其中值得注意的部分我用粗體標(biāo)識(shí)。

ExecutorService基于池化的線程來(lái)執(zhí)行用戶提交的任務(wù),通??梢院?jiǎn)單的通過(guò)Executors提供的工廠方法來(lái)創(chuàng)建ThreadPoolExecutor實(shí)例。

線程池解決的兩個(gè)問(wèn)題:1)線程池通過(guò)減少每次做任務(wù)的時(shí)候產(chǎn)生的性能消耗來(lái)優(yōu)化執(zhí)行大量的異步任務(wù)的時(shí)候的系統(tǒng)性能。2)線程池還提供了限制和管理批量任務(wù)被執(zhí)行的時(shí)候消耗的資源、線程的方法。另外ThreadPoolExecutor還提供了簡(jiǎn)單的統(tǒng)計(jì)功能,比如當(dāng)前有多少任務(wù)被執(zhí)行完了。

快速開(kāi)始

為了使得線程池適合大量不同的應(yīng)用上下文環(huán)境,ThreadPoolExecutor提供了很多可以配置的參數(shù)和可被用來(lái)擴(kuò)展的鉤子。然而,用戶還可以通過(guò)使用Executors提供的一些工廠方法來(lái)快速創(chuàng)建ThreadPoolExecutor實(shí)例。比如:

  1. 使用Executors#newCachedThreadPool可以快速創(chuàng)建一個(gè)擁有自動(dòng)回收線程功能且沒(méi)有限制的線程池。

  2. 使用Executors#newFixedThreadPool可以用來(lái)創(chuàng)建一個(gè)固定線程大小的線程池。

  3. 使用Executors#newSingleThreadExecutor可以用來(lái)創(chuàng)建一個(gè)單線程的執(zhí)行器。

如果上面的方法創(chuàng)建的實(shí)例不能滿足我們的需求,我們可以自己通過(guò)參數(shù)來(lái)配置,實(shí)例化一個(gè)實(shí)例。

關(guān)于線程數(shù)大小參數(shù)設(shè)置需要知道的

ThreadPoolExecutor會(huì)根據(jù)corePoolSize和maximumPoolSize來(lái)動(dòng)態(tài)調(diào)整線程池的大小:poolSize。

當(dāng)任務(wù)通過(guò)executor提交給線程池的時(shí)候,我們需要知道下面幾個(gè)點(diǎn):

  1. 如果這個(gè)時(shí)候當(dāng)前池子中的工作線程數(shù)小于corePoolSize,則新創(chuàng)建一個(gè)新的工作線程來(lái)執(zhí)行這個(gè)任務(wù),不管工作線程集合中有沒(méi)有線程是處于空閑狀態(tài)。

  2. 如果池子中有比corePoolSize大的但是比maximumPoolSize小的工作線程,任務(wù)會(huì)首先被嘗試著放入隊(duì)列,這里有兩種情況需要單獨(dú)說(shuō)一下:a、如果任務(wù)被成功的放入隊(duì)列,則看看是否需要開(kāi)啟新的線程來(lái)執(zhí)行任務(wù),只有當(dāng)當(dāng)前工作線程數(shù)為0的時(shí)候才會(huì)創(chuàng)建新的線程,因?yàn)橹暗木€程有可能因?yàn)槎继幱诳臻e狀態(tài)或因?yàn)楣ぷ鹘Y(jié)束而被移除。

    b、如果放入隊(duì)列失敗,則才會(huì)去創(chuàng)建新的工作線程。

  3. 如果corePoolSize和maximumPoolSize相同,則線程池的大小是固定的。

  4. 通過(guò)將maximumPoolSize設(shè)置為無(wú)限大,我們可以得到一個(gè)無(wú)上限的線程池。

  5. 除了通過(guò)構(gòu)造參數(shù)設(shè)置這幾個(gè)線程池參數(shù)之外我們還可以在運(yùn)行時(shí)設(shè)置。

核心線程WarmUp

默認(rèn)情況下,核心工作線程值在初始的時(shí)候被創(chuàng)建,當(dāng)新任務(wù)來(lái)到的時(shí)候被啟動(dòng),但是我們可以通過(guò)重寫(xiě)prestartCoreThread或prestartCoreThreads方法來(lái)改變這種行為。通常場(chǎng)景我們可以在應(yīng)用啟動(dòng)的時(shí)候來(lái)WarmUp核心線程,從而達(dá)到任務(wù)過(guò)來(lái)能夠立馬執(zhí)行的結(jié)果,使得初始任務(wù)處理的時(shí)間得到一定優(yōu)化。

定制工作線程的創(chuàng)建

新的線程是通過(guò)ThreadFactory來(lái)創(chuàng)建的,如果沒(méi)有指定,默認(rèn)的Executors#defaultThreadFactory將被使用,這個(gè)時(shí)候創(chuàng)建的線程將都屬于同一個(gè)線程組,擁有同樣的優(yōu)先級(jí)和daemon狀態(tài)。擴(kuò)展配置ThreadFactory,我們可以配置線程的名字、線程組合daemon狀態(tài)。如果調(diào)用ThreadFactory#createThread的時(shí)候失敗,將返回null,executor將不會(huì)執(zhí)行任何任務(wù)。

空閑線程回收

如果當(dāng)前池子中的工作線程數(shù)大于corePoolSize,如果超過(guò)這個(gè)數(shù)字的線程處于空閑的時(shí)間大于keepAliveTime,則這些線程將會(huì)被終止,這是一種減少不必要資源消耗的策略。這個(gè)參數(shù)可以在運(yùn)行時(shí)被改變,我們同樣可以將這種策略應(yīng)用給核心線程,我們可以通過(guò)調(diào)用allowCoreThreadTimeout來(lái)實(shí)現(xiàn)。

選擇合適的阻塞隊(duì)列

所有的阻塞隊(duì)列都可以被用來(lái)存放任務(wù),但是使用不同的隊(duì)列針對(duì)corePoolSize會(huì)表現(xiàn)不同的行為:

當(dāng)池中工作線程數(shù)小于corePoolSize的時(shí)候,每次來(lái)任務(wù)的時(shí)候都會(huì)創(chuàng)建一個(gè)新的工作線程。

當(dāng)池中工作線程數(shù)大于等于corePoolSize的時(shí)候,每次任務(wù)來(lái)的時(shí)候都會(huì)首先嘗試將線程放入隊(duì)列,而不是直接去創(chuàng)建線程。

如果放入隊(duì)列失敗,且當(dāng)先池中線程數(shù)小于maximumPoolSize的時(shí)候,則會(huì)創(chuàng)建一個(gè)工作線程。

下面主要是不同隊(duì)列策略表現(xiàn):

直接遞交:一種比較好的默認(rèn)選擇是使用SynchronousQueue,這種策略會(huì)將提交的任務(wù)直接傳送給工作線程,而不持有。如果當(dāng)前沒(méi)有工作線程來(lái)處理,即任務(wù)放入隊(duì)列失敗,則根據(jù)線程池的實(shí)現(xiàn),會(huì)引發(fā)新的工作線程創(chuàng)建,因此新提交的任務(wù)會(huì)被處理。這種策略在當(dāng)提交的一批任務(wù)之間有依賴關(guān)系的時(shí)候避免了鎖競(jìng)爭(zhēng)消耗。值得一提的是,這種策略最好是配合unbounded線程數(shù)來(lái)使用,從而避免任務(wù)被拒絕。同時(shí)我們必須要考慮到一種場(chǎng)景,當(dāng)任務(wù)到來(lái)的速度大于任務(wù)處理的速度,將會(huì)引起無(wú)限制的線程數(shù)不斷的增加。

無(wú)界隊(duì)列:使用無(wú)界隊(duì)列如LinkedBlockingQueue沒(méi)有指定最大容量的時(shí)候,將會(huì)引起當(dāng)核心線程都在忙的時(shí)候,新的任務(wù)被放在隊(duì)列上,因此,永遠(yuǎn)不會(huì)有大于corePoolSize的線程被創(chuàng)建,因此maximumPoolSize參數(shù)將失效。這種策略比較適合所有的任務(wù)都不相互依賴,獨(dú)立執(zhí)行。舉個(gè)例子,如網(wǎng)頁(yè)服務(wù)器中,每個(gè)線程獨(dú)立處理請(qǐng)求。但是當(dāng)任務(wù)處理速度小于任務(wù)進(jìn)入速度的時(shí)候會(huì)引起隊(duì)列的無(wú)限膨脹。

有界隊(duì)列:有界隊(duì)列如ArrayBlockingQueue幫助限制資源的消耗,但是不容易控制。隊(duì)列長(zhǎng)度和maximumPoolSize這兩個(gè)值會(huì)相互影響,使用大的隊(duì)列和小maximumPoolSize會(huì)減少CPU的使用、操作系統(tǒng)資源、上下文切換的消耗,但是會(huì)降低吞吐量,如果任務(wù)被頻繁的阻塞如IO線程,系統(tǒng)其實(shí)可以調(diào)度更多的線程。使用小的隊(duì)列通常需要大maximumPoolSize,從而使得CPU更忙一些,但是又會(huì)增加降低吞吐量的線程調(diào)度的消耗??偨Y(jié)一下是IO密集型可以考慮多些線程來(lái)平衡CPU的使用,CPU密集型可以考慮少些線程減少線程調(diào)度的消耗。

選擇適合的拒絕策略

當(dāng)新的任務(wù)到來(lái)的而線程池被關(guān)閉的時(shí)候,或線程數(shù)和隊(duì)列已經(jīng)達(dá)到上限的時(shí)候,我們需要去做一個(gè)決定,怎么拒絕這些任務(wù)。下面介紹一下常用的策略:

ThreadPoolExecutor#AbortPolicy:這個(gè)策略直接拋出RejectedExecutionException異常。

ThreadPoolExecutor#CallerRunsPolicy:這個(gè)策略將會(huì)使用Caller線程來(lái)執(zhí)行這個(gè)任務(wù),這是一種feedback策略,可以降低任務(wù)提交的速度。

ThreadPoolExecutor#DiscardPolicy:這個(gè)策略將會(huì)直接丟棄任務(wù)。

ThreadPoolExecutor#DiscardOldestPolicy:這個(gè)策略將會(huì)把任務(wù)隊(duì)列頭部的任務(wù)丟棄,然后重新嘗試執(zhí)行,如果還是失敗則繼續(xù)實(shí)施策略。

除了上面的幾種策略,我們也可以通過(guò)實(shí)現(xiàn)RejectedExecutionHandler來(lái)實(shí)現(xiàn)自己的策略。

利用Hook嵌入你的行為

ThreadPoolExecutor提供了protected類型可以被覆蓋的鉤子方法,允許用戶在任務(wù)執(zhí)行之前會(huì)執(zhí)行之后做一些事情。我們可以通過(guò)它來(lái)實(shí)現(xiàn)比如初始化ThreadLocal、收集統(tǒng)計(jì)信息、如記錄日志等操作。這類Hook如beforeExecute和afterExecute。另外還有一個(gè)Hook可以用來(lái)在任務(wù)被執(zhí)行完的時(shí)候讓用戶插入邏輯,如rerminated。

如果hook方法執(zhí)行失敗,則內(nèi)部的工作線程的執(zhí)行將會(huì)失敗或被中斷。

可訪問(wèn)的隊(duì)列

getQueue方法可以用來(lái)訪問(wèn)queue隊(duì)列以進(jìn)行一些統(tǒng)計(jì)或者debug工作,我們不建議用作其他用途。同時(shí)remove方法和purge方法可以用來(lái)將任務(wù)從隊(duì)列中移除。

關(guān)閉線程池

當(dāng)線程池不在被引用并且工作線程數(shù)為0的時(shí)候,線程池將被終止。我們也可以調(diào)用shutdown來(lái)手動(dòng)終止線程池。如果我們忘記調(diào)用shutdown,為了讓線程資源被釋放,我們還可以使用keepAliveTime和allowCoreThreadTimeOut來(lái)達(dá)到目的。

寫(xiě)在最后

JAVA本身提供的API已經(jīng)可以讓我們快速的進(jìn)行基于線程池的多線程開(kāi)發(fā),但是我們必須要為我們寫(xiě)的代碼負(fù)責(zé),每一個(gè)參數(shù)的設(shè)置和策略的選擇跟不同應(yīng)用場(chǎng)景有絕對(duì)的關(guān)系。然而對(duì)于不同參數(shù)和不同策略的選擇并不是一件容易的事情,我們必須要先回答一些基礎(chǔ)問(wèn)題:每創(chuàng)建一個(gè)線程,操作系統(tǒng)為我們做了哪些事情,這個(gè)線程的操作系統(tǒng)資源消耗主要在哪部分?假如我的應(yīng)用場(chǎng)景是IO密集型的,那么我需要更多的線程還是更少的線程?假如我們的CPU操作和IO操作大概各占一半的話我們又需要如何選擇?等等一些列問(wèn)題。我認(rèn)為、多線程開(kāi)發(fā)是一件很容易的事情也是一件很不容易的事情。

更好的使用JAVA線程池

更多JAVA開(kāi)發(fā)/Nginx/dubbo/SQL/ActiveMQ/高并發(fā)/架構(gòu)/分布式/性能優(yōu)化JAVA交流免費(fèi)視頻群:578455330

    本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請(qǐng)注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購(gòu)買(mǎi)等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊一鍵舉報(bào)。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評(píng)論

    發(fā)表

    請(qǐng)遵守用戶 評(píng)論公約

    類似文章 更多

    黄片在线免费看日韩欧美| 国产精品内射视频免费| 国产欧美一区二区色综合| 黄片免费播放一区二区| 国产丝袜极品黑色高跟鞋| 精品精品国产欧美在线| 中国美女偷拍福利视频| 国产精品流白浆无遮挡| 欧美日韩成人在线一区| 国产精品免费视频视频| 欧美国产日韩变态另类在线看| 91午夜少妇极品福利| 五月婷婷六月丁香亚洲| 亚洲男人天堂网在线视频| 91国自产精品中文字幕亚洲| 在线观看视频成人午夜| 日本欧美一区二区三区高清| 国产午夜精品亚洲精品国产| 亚洲中文字幕高清乱码毛片| 丰满少妇被猛烈插入在线观看| 日本午夜免费福利视频| 久久热麻豆国产精品视频| 久久免费精品拍拍一区二区| 欧美国产极品一区二区| 日韩欧美国产精品自拍| 91在线爽的少妇嗷嗷叫| 欧美日韩免费黄片观看| 五月婷婷综合激情啪啪| 亚洲二区欧美一区二区| 日韩欧美综合在线播放| 熟女免费视频一区二区| 色婷婷日本视频在线观看| 久久人妻人人澡人人妻| 日本欧美三级中文字幕| 亚洲欧美日韩在线中文字幕| 国产亚洲精品久久久优势| 搡老熟女老女人一区二区| 日韩黄片大全免费在线看| 亚洲国产精品一区二区毛片| 亚洲二区欧美一区二区| 亚洲精品一区二区三区免|