1. 深入多線程面試連環(huán)炮【第二彈】

        共 5429字,需瀏覽 11分鐘

         ·

        2021-12-17 18:14

        在這之前的幾篇文章:



        1、什么是線程池


        線程的創(chuàng)建和銷毀是一個“重”操作,所以我們需要避免線程頻繁地創(chuàng)建與銷毀,因此我們需要緩存一批線程,讓它們時刻準(zhǔn)備著執(zhí)行任務(wù)

        ?

        目標(biāo)已經(jīng)很清晰了,弄一個池子,里面存放約定數(shù)量的線程,這就是線程池,一種池化技術(shù)


        如果線程數(shù)太少無法充分利用?CPU?,太多的話由于上下文切換的消耗又得不償失,所以我們需要評估系統(tǒng)所要承載的并發(fā)量和所執(zhí)行任務(wù)的特性,得出大致需要多少個線程數(shù)才能充分利用?CPU,因此需要控制線程數(shù)量


        多線程技術(shù)主要解決處理器單元內(nèi)多個線程執(zhí)行的問題,它可以顯著減少處理器單元的閑置時間,增加處理器單元的吞吐能力

        ???

        假設(shè)一個服務(wù)器完成一項任務(wù)所需時間為:T1 創(chuàng)建線程時間,T2 在線程中執(zhí)行任務(wù)的時間,T3 銷毀線程時間

        ?

        如果:T1 + T3 遠(yuǎn)大于 T2,則可以采用線程池,以提高服務(wù)器性能

        ?

        線程池技術(shù)正是關(guān)注如何縮短或調(diào)整T1,T3時間的技術(shù),從而提高服務(wù)器程序性能的。它把T1,T3分別安排在服務(wù)器程序的啟動和結(jié)束的時間段或者一些空閑的時間段,這樣在服務(wù)器程序處理客戶請求時,不會有T1,T3的開銷了


        線程池不僅調(diào)整T1,T3產(chǎn)生的時間段,而且它還顯著減少了創(chuàng)建線程的數(shù)目


        2、線程池優(yōu)點,為什么要使用線程池

        new Thread 缺點


        每次new Thread新建對象性能差


        線程缺乏統(tǒng)一管理,可能無限制新建線程,相互之間競爭,及可能占用過多系統(tǒng)資源導(dǎo)致死機或oom


        缺乏更多功能,如定時執(zhí)行、定期執(zhí)行、線程中斷


        為什么要用線程池


        減少了創(chuàng)建和銷毀線程的次數(shù),每個工作線程都可以被重復(fù)利用,可執(zhí)行多個任務(wù)。


        可以根據(jù)系統(tǒng)的承受能力,調(diào)整線程池中工作線線程的數(shù)目,防止因為消耗過多的內(nèi)存,而把服務(wù)器累趴下(每個線程需要大約1MB內(nèi)存,線程開的越多,消耗的內(nèi)存也就越大,最后死機)。

        ?

        ThreadPool優(yōu)點


        減少了創(chuàng)建和銷毀線程的次數(shù),每個工作線程都可以被重復(fù)利用,可執(zhí)行多個任務(wù)

        ?

        可以根據(jù)系統(tǒng)的承受能力,調(diào)整線程池中工作線線程的數(shù)目,防止因為因為消耗過多的內(nèi)存,而把服務(wù)器累趴下(每個線程需要大約1MB內(nèi)存,線程開的越多,消耗的內(nèi)存也就越大,最后死機)

        ?

        減少在創(chuàng)建和銷毀線程上所花的時間以及系統(tǒng)資源的開銷


        如不使用線程池,有可能造成系統(tǒng)創(chuàng)建大量線程而導(dǎo)致消耗完系統(tǒng)內(nèi)存



        3、常用的線程池


        第1種是:固定大小線程池,特點是線程數(shù)固定,使用無界隊列,適用于任務(wù)數(shù)量不均勻的場景、對內(nèi)存壓力不敏感,但系統(tǒng)負(fù)載比較敏感的場景

        ?

        第2種是:Cached線程池,特點是不限制線程數(shù),適用于要求低延遲的短期任務(wù)場景

        ?

        第3種是:單線程線程池,也就是一個線程的固定線程池,適用于需要異步執(zhí)行但需要保證任務(wù)順序的場景

        ?

        第4種是:Scheduled線程池,適用于定期執(zhí)行任務(wù)場景,支持按固定頻率定期執(zhí)行和按固定延時定期執(zhí)行兩種方式

        ?

        第5種是:工作竊取線程池,使用的ForkJoinPool,是固定并行度的多任務(wù)隊列,適合任務(wù)執(zhí)行時長不均勻的場景


        4、聽說過Executors嗎


        Java里面線程池的頂級接口是Executor,但是嚴(yán)格意義上講Executor并不是一個線程池,而只是一個執(zhí)行線程的工具。真正的線程池接口是ExecutorService



        Executors是一個工具類,類里面提供了一些靜態(tài)工廠,生成一些常用的線程池

        ?

        Executors提供四種線程池

        ?

        newCachedThreadPool創(chuàng)建一個可緩存線程池,如果線程池長度超過處理需要,可靈活回收空閑線程,若無可回收,則新建線程

        ?

        newFixedThreadPool 創(chuàng)建一個定長線程池,可控制線程最大并發(fā)數(shù),超出的線程會在隊列中等待

        ?

        newScheduledThreadPool 創(chuàng)建一個定長線程池,支持定時及周期性任務(wù)執(zhí)行


        newSingleThreadExecutor 創(chuàng)建一個單線程化的線程池,它只會用唯一的工作線程來執(zhí)行任務(wù),保證所有任務(wù)按照指定順序(FIFO, LIFO, 優(yōu)先級)執(zhí)行

        ?

        一般都不用Executors提供的線程創(chuàng)建方式,使用ThreadPoolExecutor創(chuàng)建線程池


        5、那你說說為什么阿里巴巴不建議使用Executors靜態(tài)工廠構(gòu)建線程池

        在阿里巴巴Java開發(fā)手冊中提到,使用Executors創(chuàng)建線程池可能會導(dǎo)致OOM(OutOfMemory ,內(nèi)存溢出),真正的導(dǎo)致OOM的其實是LinkedBlockingQueue.offer方法

        ?

        底層是通過LinkedBlockingQueue實現(xiàn)的, LinkedBlockingQueue是一個用鏈表實現(xiàn)的有界阻塞隊列,容量可以選擇進(jìn)行設(shè)置,不設(shè)置的話,將是一個無邊界的阻塞隊列,最大長度為Integer.MAX_VALUE

        ?

        問題就出在:不設(shè)置的話,將是一個無邊界的阻塞隊列,最大長度為Integer.MAX_VALUE。也就是說,如果我們不設(shè)置LinkedBlockingQueue的容量的話,其默認(rèn)容量將會是Integer.MAX_VALUE

        ?

        對于一個無邊界隊列來說,是可以不斷的向隊列中加入任務(wù)的,這種情況下就有可能因為任務(wù)過多而導(dǎo)致內(nèi)存溢出問題

        ?

        避免使用Executors創(chuàng)建線程池,主要是避免使用其中的默認(rèn)實現(xiàn),那么我們可以自己直接調(diào)用ThreadPoolExecutor的構(gòu)造函數(shù)來自己創(chuàng)建線程池。在創(chuàng)建的同時,給BlockQueue指定容量就可以了


        6、線程池核心參數(shù)有哪些

        第1個參數(shù):設(shè)置核心線程數(shù)。默認(rèn)情況下核心線程會一直存活

        ?

        第2個參數(shù):設(shè)置最大線程數(shù)。決定線程池最多可以創(chuàng)建的多少線程

        ?

        第3個參數(shù)和第4個參數(shù):用來設(shè)置線程空閑時間,和空閑時間的單位,當(dāng)線程閑置超過空閑時間就會被銷毀??梢酝ㄟ^AllowCoreThreadTimeOut方法來允許核心線程被回收

        ?

        第5個參數(shù):設(shè)置緩沖隊列,圖中左下方的三個隊列是設(shè)置線程池時常使用的緩沖隊列

        ?

        其中Array Blocking Queue是一個有界隊列,就是指隊列有最大容量限制。Linked Blocking Queue是無界隊列,就是隊列不限制容量。最后一個是Synchronous Queue,是一個同步隊列,內(nèi)部沒有緩沖區(qū)

        ?

        第6個參數(shù):設(shè)置線程池工廠方法,線程工廠用來創(chuàng)建新線程,可以用來對線程的一些屬性進(jìn)行定制,例如線程的Group、線程名、優(yōu)先級等。一般使用默認(rèn)工廠類即可

        ?

        第7個參數(shù):設(shè)置線程池滿時的拒絕策略

        ?

        ThreadPoolExecutor默認(rèn)有四個拒絕策略:


        ThreadPoolExecutor.AbortPolicy() 直接拋出異常RejectedExecutionException,這個是默認(rèn)的拒絕策略


        ThreadPoolExecutor.CallerRunsPolicy() 直接在提交失敗時,由提交任務(wù)的線程直接執(zhí)行提交的任務(wù)


        ThreadPoolExecutor.DiscardPolicy() 直接丟棄后來的任務(wù)


        ThreadPoolExecutor.DiscardOldestPolicy() 丟棄在隊列中最早提交的任務(wù)


        7、線程池的工作原理


        我們向線程提交任務(wù)時可以使用Execute和Submit,區(qū)別就是Submit可以返回一個Future對象,通過Future對象可以了解任務(wù)執(zhí)行情況,可以取消任務(wù)的執(zhí)行,還可獲取執(zhí)行結(jié)果或執(zhí)行異常。Submit最終也是通過Execute執(zhí)行的

        ?

        線程池提交任務(wù)時的執(zhí)行順序如下:

        ?

        向線程池提交任務(wù)時,會首先判斷線程池中的線程數(shù)是否大于設(shè)置的核心線程數(shù),如果不大于,就創(chuàng)建一個核心線程來執(zhí)行任務(wù)

        ?

        如果大于核心線程數(shù),就會判斷緩沖隊列是否滿了,如果沒有滿,則放入隊列,等待線程空閑時執(zhí)行任務(wù)


        如果隊列已經(jīng)滿了,則判斷是否達(dá)到了線程池設(shè)置的最大線程數(shù),如果沒有達(dá)到,就創(chuàng)建新線程來執(zhí)行任務(wù)


        如果已經(jīng)達(dá)到了最大線程數(shù),則執(zhí)行指定的拒絕策略。這里需要注意隊列的判斷與最大線程數(shù)判斷的順序,不要搞反

        ?

        如果你提交任務(wù)時,線程池隊列已滿,這時會發(fā)生什么?

        ?

        如果你使用的LinkedBlockingQueue,也就是無界隊列的話,沒關(guān)系,繼續(xù)添加任務(wù)到阻塞隊列中等待執(zhí)行,因為LinkedBlockingQueue可以近乎認(rèn)為是一個無窮大的隊列,可以無限存放任務(wù)

        ?

        如果你使用的是有界隊列比方說ArrayBlockingQueue的話,任務(wù)首先會被添加到ArrayBlockingQueue中,ArrayBlockingQueue滿了,則會使用拒絕策略RejectedExecutionHandler處理滿了的任務(wù),默認(rèn)是AbortPolicy


        8、高并發(fā)、任務(wù)執(zhí)行時間短的業(yè)務(wù)怎樣使用線程池?并發(fā)不高、任務(wù)執(zhí)行時間長的業(yè)務(wù)怎樣使用線程池?并發(fā)高、業(yè)務(wù)執(zhí)行時間長的業(yè)務(wù)怎樣使用線程池?

        高并發(fā)、任務(wù)執(zhí)行時間短的業(yè)務(wù),線程池線程數(shù)可以設(shè)置為CPU核數(shù)+1,減少線程上下文的切換

        ?

        并發(fā)不高、任務(wù)執(zhí)行時間長的業(yè)務(wù)要分情況來討論

        ?

        假如是業(yè)務(wù)時間長集中在IO操作上,也就是IO密集型的任務(wù),因為IO操作并不占用CPU,所以不要讓所有的CPU閑下來,可以加大線程池中的線程數(shù)目,讓CPU處理更多的業(yè)務(wù)

        ?

        假如是業(yè)務(wù)時間長集中在計算操作上,也就是計算密集型任務(wù),這個就沒辦法了,線程數(shù)設(shè)置為CPU核數(shù)+1,線程池中的線程數(shù)設(shè)置得少一些,減少線程上下文的切換

        ?

        并發(fā)高、業(yè)務(wù)執(zhí)行時間長,解決這種類型任務(wù)的關(guān)鍵不在于線程池而在于整體架構(gòu)的設(shè)計,看看這些業(yè)務(wù)里面某些數(shù)據(jù)是否能做緩存是第一步,增加服務(wù)器是第二步,至于線程池的設(shè)置,參考上面的設(shè)置即可


        最后,業(yè)務(wù)執(zhí)行時間長的問題,也可能需要分析一下,看看能不能使用中間件對任務(wù)進(jìn)行拆分和解耦


        9、聽說過ThreadLocal嗎


        看我的這一篇介紹


        10、簡單介紹下阻塞隊列吧

        阻塞隊列是一個在隊列基礎(chǔ)上又支持了兩個附加操作的隊列

        ?

        支持阻塞的插入方法:隊列滿時,隊列會阻塞插入元素的線程,直到隊列不滿。支持阻塞的移除方法:隊列空時,獲取元素的線程會等待隊列變?yōu)榉强?/span>



        阻塞隊列的應(yīng)用場景

        ?

        阻塞隊列常用于生產(chǎn)者和消費者的場景,生產(chǎn)者是向隊列里添加元素的線程,消費者是從隊列里取元素的線程。簡而言之,阻塞隊列是生產(chǎn)者用來存放元素、消費者獲取元素的容器

        ?

        1、ArrayBlockingQueue 數(shù)組結(jié)構(gòu)組成的有界阻塞隊列

        ?

        此隊列按照先進(jìn)先出(FIFO)的原則對元素進(jìn)行排序,但是默認(rèn)情況下不保證線程公平的訪問隊列,即如果隊列滿了,那么被阻塞在外面的線程對隊列訪問的順序是不能保證線程公平(即先阻塞,先插入)的。

        ?

        2、LinkedBlockingQueue一個由鏈表結(jié)構(gòu)組成的有界阻塞隊列,此隊列按照先出先進(jìn)的原則對元素進(jìn)行排序

        ?

        3、PriorityBlockingQueue支持優(yōu)先級的無界阻塞隊列

        ?

        4、DelayQueue支持延時獲取元素的無界阻塞隊列,即可以指定多久才能從隊列中獲取當(dāng)前元素

        ?

        5、SynchronousQueue不存儲元素的阻塞隊列,每一個put必須等待一個take操作,否則不能繼續(xù)添加元素。并且支持公平訪問隊列。

        ?

        6、LinkedTransferQueue由鏈表結(jié)構(gòu)組成的無界阻塞TransferQueue隊列。相對于其他阻塞隊列,多了tryTransfertransfer方法

        ?

        transfer方法

        ?

        如果當(dāng)前有消費者正在等待接收元素(take或者待時間限制的poll方法),transfer可以把生產(chǎn)者傳入的元素立刻傳給消費者。如果沒有消費者等待接收元素,則將元素放在隊列的tail節(jié)點,并等到該元素被消費者消費了才返回

        ?

        tryTransfer方法

        ?

        用來試探生產(chǎn)者傳入的元素能否直接傳給消費者。,如果沒有消費者在等待,則返回false。和上述方法的區(qū)別是該方法無論消費者是否接收,方法立即返回。而transfer方法是必須等到消費者消費了才返回

        ?

        7、LinkedBlockingDeque鏈表結(jié)構(gòu)的雙向阻塞隊列,優(yōu)勢在于多線程入隊時,減少一半的競爭

        結(jié)束語


        感謝大家能夠做我最初的讀者和傳播者,請大家相信,只要你給我一份愛,我終究會還你們一頁情的。


        Captain會持續(xù)更新技術(shù)文章,和生活中的暴躁文章,歡迎大家關(guān)注【Java賊船】,成為船長的學(xué)習(xí)小伙伴,和船長一起乘千里風(fēng)、破萬里浪


        哦對了,后續(xù)所有的文章都會更新到這里


        https://github.com/DayuMM2021/Java




        瀏覽 66
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

        分享
        舉報
        評論
        圖片
        表情
        推薦
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

        分享
        舉報
          
          

            1. 淫色淫射淫影院 | 爱情岛成人论坛 | 青娱乐一区二区 | 国产精品久久亚洲7777 | 美女扣逼 |