<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          圖解 | 阿里面試官愛問的線程池到底是什么?

          共 4993字,需瀏覽 10分鐘

           ·

          2021-02-26 14:08

          前言

          前幾天小強去阿里巴巴面試Java崗,止步于二面。

          他和我訴苦自己被虐的多慘多慘,特別是深挖線程和線程池的時候,居然被問到不知道如何作答。

          對于他的遭遇,結(jié)合他過了一面的那個嘚瑟樣,我深表同情(加大力度)!

          好了,不開玩笑了,在和小強的面試題中,我選取了幾個比較典型的線程和線程池的問題。

          Java中的線程和操作系統(tǒng)的線程有什么關(guān)系?

          調(diào)用start方法是如何執(zhí)行run方法的?

          線程池提交任務(wù)有哪幾種方式?分別有什么區(qū)別?

          談?wù)勀銓ψ枞犃械睦斫狻?/p>

          常見的線程池有哪些?為什么阿里不允許使用 Executors 去創(chuàng)建線程池?

          線程池任務(wù)調(diào)度的流程大致講一下。

          線程池里面的線程執(zhí)行異常了會怎么樣?

          核心線程和非核心線程是如何區(qū)分的?

          想要答對這些問題,并不是很難,但是想要答好,我覺得是非常考驗個人功底的。

          為了弄清這些問題,我連夜加急,采訪了“線程”,下面是線程的自述。


          我是誰

          我是一個線程,一個底層的打工人。

          總有人把我和進程搞混,但其實我和進程的區(qū)別很大。

          進程是程序的一次執(zhí)行,CPU的資源都是分發(fā)給進程而不是分發(fā)給我們線程,進程是資源分配的最小單位,一個進程可以包含很多向我這樣的線程。

          我們線程是CPU調(diào)度執(zhí)行的最小單位,真正的打工人。


          Java中的線程

          在Java里面,我的名字叫做java.lang.Thread。

          需要注意的是,調(diào)用run方法和執(zhí)行一個普通方法沒有區(qū)別。想要真正的創(chuàng)建一個線程并啟動,需要調(diào)用我的start方法。

          有一點我必須告訴你,就是我也是有小弟的。

          在JVM里面,我有一個JavaThread的小弟,他幫我聯(lián)系操作系統(tǒng)的osthread線程。

          調(diào)用我的start方法之后,具體的執(zhí)行流程是這樣的:

          當(dāng)然了,這個過程省略了很多細(xì)節(jié),不過很明確的是,我和內(nèi)核線程是一一對應(yīng)的。

          調(diào)度我就相當(dāng)于調(diào)度內(nèi)核線程,而調(diào)度內(nèi)核線程需要在用戶態(tài)和內(nèi)核態(tài)之間切換,這個過程開銷是非常大的。

          所以,創(chuàng)建我成本是很高的,一定要慎重。

          線程池

          和你們?nèi)祟愐粯樱乙灿兄实囊簧矔?jīng)歷出生(創(chuàng)建)、奮斗(Running)、死亡(銷毀)等過程,今天我主要和你講述的是我打工奮斗的生活。

          原來我是打零工的,有人需要我的時候就創(chuàng)建一個我,等我完成工作就把我銷毀。

          上面也提到過,我和內(nèi)核線程是一對一的,創(chuàng)建和銷毀的過程是非常消耗資源的,所以這樣的成本非常高。

          于是,有人就想了一個辦法,開了一個公司,也就是你們說的線程池。

          線程池公司統(tǒng)一管理調(diào)度我們線程。我們在線程池里面重復(fù)著等待工作——完成工作的步驟。

          這樣我就可以日復(fù)一日年復(fù)一年的重復(fù)打工了,這種提供了減少對象數(shù)量從而改善應(yīng)用所需的對象結(jié)構(gòu)的方式的模式,被你們?nèi)祟惤凶觥跋碓J健薄?/p>

          線程池公司有很多種,但都離不開這幾個主要指標(biāo):

          • corePoolSize:公司正式員工人數(shù)。
          • maximumPoolSize:正式工+臨時工最大數(shù)量。
          • keepAliveTime:臨時工多久沒做事情會被開除。
          • unit:臨時工沒做事情會被開除的時間單位。
          • workQueue:公司業(yè)務(wù)接收部門。
          • threadFactory:行政部,負(fù)責(zé)招聘培訓(xùn)員工的。
          • handler:業(yè)務(wù)部接收業(yè)務(wù)到達(dá)上限了的處理方式。

          阻塞隊列

          線程池中的workQueue是一個阻塞隊列,用于存放線程池未能及時處理執(zhí)行的任務(wù)。

          它的存在既解耦了任務(wù)的提交與執(zhí)行,又能起到一個緩沖的作用。

          阻塞隊列有很多,下面我?guī)懔私庖幌鲁R姷淖枞犃小?/p>

          ArrayBlockingQueue

          基于數(shù)組實現(xiàn)的有界阻塞隊列,創(chuàng)建的時候需要指定容量。此類型的隊列按照FIFO(先進先出)的規(guī)則對元素進行排序。

          LinkedBlockingQueue

          基于鏈表實現(xiàn)阻塞隊列,默認(rèn)大小為Integer.MAX_VALUE。按照FIFO(先進先出)的規(guī)則對元素進行排序。

          SynchronousQueue

          一個不存儲元素的阻塞隊列。每一個put操作必須阻塞等待其他線程的take操作,take操作也必須等待其他線程的put操作。

          PriorityBlockingQueue

          一個基于數(shù)組利用堆結(jié)構(gòu)實現(xiàn)優(yōu)先級效果的無界隊列,默認(rèn)自然序排序,也可以自己實現(xiàn)compareTo方法自定義排序規(guī)則。

          DelayedWorkQueue

          一個實現(xiàn)了優(yōu)先級隊列功能且實現(xiàn)了延遲獲取的無界隊列,在創(chuàng)建元素時,可以指定多久多久才能在隊列中獲取當(dāng)前元素。只有延時期滿了后才能從隊列中獲取元素。

          拒絕策略

          當(dāng)任務(wù)隊列滿了之后,如果還有任務(wù)提交過來,會觸發(fā)拒絕策略,常見的拒絕策略有:

          • AbortPolicy:丟棄任務(wù)并拋出異常,默認(rèn)該方式。

          • CallerRunsPolicy:由調(diào)用線程自己處理該任務(wù)。誰調(diào)用,誰處理。

          • DiscardPolicy:丟棄任務(wù),但是不拋出異常。

          • DiscardOldestPolicy:拋棄任務(wù)隊列中最舊的任務(wù),也就是最先加入隊列的,再把這個新任務(wù)添加進去。先從任務(wù)隊列中彈出最先加入的任務(wù),空出一個位置,然后再次執(zhí)行execute方法把任務(wù)加入隊列。

          當(dāng)然,除了以上這幾種拒絕策略,你也可以根據(jù)實際的業(yè)務(wù)場景和業(yè)務(wù)需求去自定義拒絕策略,只需要實現(xiàn)RejectedExecutionHander接口,自定義里面的rejectedExecution方法。

          運行流程

          我們每個線程會被包裝成Worker,線程池里面有一個HashSet存放Worker。

          當(dāng)有任務(wù)提交過來之后:

          1. 首先檢測線程池運行狀態(tài),如果不是RUNNING,則直接拒絕,線程池要保證在RUNNING的狀態(tài)下執(zhí)行任務(wù)。
          2. 如果線程池中Worker的數(shù)量小于核心線程數(shù),就會去創(chuàng)建一個新的線程,也就是招聘一個正式工讓他執(zhí)行任務(wù)。
          3. 如果Worker的數(shù)量大于或者等于核心線程數(shù),就會把任務(wù)放到阻塞任務(wù)隊列里面。
          4. 如果任務(wù)隊列滿了還有任務(wù)過來,如果臨時工名額沒有滿(workerCount < maximumPoolSize),就去招聘臨時工讓臨時工執(zhí)行任務(wù)。如果臨時工名額都滿了,觸發(fā)任務(wù)拒絕策略。

          總結(jié)而言,就是核心線程能干的事情盡量不去創(chuàng)建非核心線程,這是線程池很關(guān)鍵的一點。

          new ThreadPoolExecutor(4,  80L, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<Runnable>(4));

          以這個線程池為例,下面是他的任務(wù)提交和執(zhí)行流程:

          有哪些線程池

          我有過四段工作經(jīng)歷,每段經(jīng)歷都有著精彩的故事。

          SingleThreadExecutor

          SingleThreadExecutor是我加入的第一家線程池,這是一家創(chuàng)業(yè)公司,整個線程池就只有我一個線程。

          所有的任務(wù)都由我干,而且任務(wù)隊列是一個無界隊列。就是說,打工的線程只有我一個,但是需求任務(wù)可以是無限多。

          在需求任務(wù)很多的時候,經(jīng)常出現(xiàn)任務(wù)處理不過來的情況,導(dǎo)致任務(wù)堆積,出現(xiàn)OOM。

          但因為所有的活都是我干,沒有繁瑣的溝通成本,不需要處理線程同步的問題,這算是這種線程池的一個優(yōu)點吧。

          這種線程池適用于并發(fā)量不大且需要任務(wù)順序執(zhí)行的場景。

          FixedThreadPool

          后來公司倒閉了,我又加入了一個叫FixedThreadPool的線程池。

          FixedThreadPool和SingleThreadExecutor唯一不同的地方就是核心線程的數(shù)量,F(xiàn)ixedThreadPool可以招收很多的打工線程。

          在這里,我不再是孤軍奮斗了,我有了一群共同打拼的小伙伴,大家一起完成任務(wù),一起承擔(dān)壓力。

          可這種線程池還是存在一個問題——任務(wù)隊列是無界的,需求任務(wù)過多的話,還是會造成OOM。

          這種線程池線程數(shù)固定,且不被回收,線程與線程池的生命周期同步的線程池,適用于任務(wù)量比較固定但耗時長的任務(wù)。

          CachedThreadPool

          后來,為了離家更近,我離職了。加入了一家叫CachedThreadPool的線程池,進去之后,卻發(fā)現(xiàn)這是一家外包公司。

          這種線程池里面沒有一個核心線程(正式工),一有需求就去招聘一個非核心線程(臨時工)。

          如果一個線程任務(wù)干完了之后,60秒之后沒有新的任務(wù)就會被辭退。

          這種線程池的任務(wù)隊列采用的是SynchronousQueue,這個隊列是無法插入任務(wù)的,一有任務(wù)就創(chuàng)建一個線程執(zhí)行,如果并發(fā)高且任務(wù)耗時長,創(chuàng)建太多線程也是可能導(dǎo)致OOM的。所以CachedThreadPool比較適合任務(wù)量大但耗時少的任務(wù)。

          ScheduleThreadPool

          經(jīng)歷了外面的風(fēng)風(fēng)雨雨,我覺得還是找份固定的工作比較可靠,于是我加入了一家叫做ScheduleThreadPool的國企。

          在這里,工作比較的輕松,多數(shù)情況下,我只需要在固定的時間干固定的活。

          任務(wù)忙不過來的時候,公司也會招聘一些臨時工幫忙處理,臨時工干完活就會被辭退。

          綜合來說,這類線程池適用于執(zhí)行定時任務(wù)和具體固定周期的重復(fù)任務(wù)。由于采用的任務(wù)隊列是DelayedWorkQueue無界隊列,所以也是有OOM的風(fēng)險的。


          總結(jié)

          好了,關(guān)于線程的故事就告一段落了。關(guān)于線程池的應(yīng)用實踐,我們下次再聊。

          文章開頭的面試題在大部分在文中都能找到答案,對于沒有提到的,這里做一個補充:

          1. 線程池提交任務(wù)有哪幾種方式?分別有什么區(qū)別?

          有execute和submit兩種方式

          • execute只能提交Runnable類型的任務(wù),無返回值。submit既可以提交Runnable類型的任務(wù),也可以提交Callable類型的任務(wù),會有一個類型為Future的返回值,但當(dāng)任務(wù)類型為Runnable時,返回值為null。

          • execute在執(zhí)行任務(wù)時,如果遇到異常會直接拋出,而submit不會直接拋出,只有在使用Future的get方法獲取返回值時,才會拋出異常。

          2. 線程池里面的線程執(zhí)行異常了會怎么樣?

          如果一個線程執(zhí)行任務(wù)的過程中出現(xiàn)異常,那么這個線程對應(yīng)的Worker會被移出線程池,該線程也會被銷毀回收。

          同時會通過指定的線程工廠創(chuàng)建一個線程,并封裝成Worker放入線程池代替移除的Worker。

          3. 核心線程能被回收嗎?

          核心線程默認(rèn)不會被回收。但是可以調(diào)用allowCoreThreadTimeOut讓核心線程可以被回收。

          需要注意的是,調(diào)用這個方法的線程池必須將keepAliveTime設(shè)置為大于0,否則會拋出異常。

          4. 核心線程和非核心線程是如何區(qū)分的?

          核心線程和非核心線程是一個抽象概念,只是用于更好的表述線程池的運行邏輯,實際上都對應(yīng)操作系統(tǒng)的osThread,都是重量級線程。

          在新增Worker的時候,通過一個boolean表達(dá)是核心線程還是非核心線程,本質(zhì)上兩者沒有什么不同。

          5. 為什么阿里不允許使用 Executors 去創(chuàng)建線程池?

          FixedThreadPool 和 SingleThreadPool:允許的請求隊列長度為 Integer.MAX_VALUE,可能會堆積大量的請求,從而導(dǎo)致 OOM。

          CachedThreadPool:允許的創(chuàng)建線程數(shù)量為 Integer.MAX_VALUE,可能會創(chuàng)建大量的線程,從而導(dǎo)致 OOM。

          總結(jié)來說就是,使用Executors創(chuàng)建線程池會容易忽視線程池的一些屬性,使用不當(dāng)容易造成資源耗盡。

          寫在最后

          這個世界上或許沒有線程,又或許人人都是線程。

          好了,今天的文章就到這里了。

          最后,感謝你的閱讀!


          推薦閱讀:



          最近面試BAT,整理一份面試資料Java面試BAT通關(guān)手冊,覆蓋了Java核心技術(shù)、JVM、Java并發(fā)、SSM、微服務(wù)、數(shù)據(jù)庫、數(shù)據(jù)結(jié)構(gòu)等等。

          獲取方式:關(guān)注公眾號并回復(fù) Java 領(lǐng)取,更多內(nèi)容陸續(xù)奉上。

          明天見(??ω??)??

          瀏覽 101
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

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

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  国产精品99久久久久久猫咪 | 天天性综合| 天天肏| 精品成人18秘 亚洲AV蜜臀 | 日本岛国视频在线观看一区二区三区 |