<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>

          看看提交任務到線程池的源碼執(zhí)行流程是什么樣子的

          共 4457字,需瀏覽 9分鐘

           ·

          2021-11-08 18:35

          ? 我們在上一節(jié)中對線程池構(gòu)造的核心參數(shù)進行了細致的分析,一步一圖的對每個參數(shù)的含義、作用都做了圖文并茂的分析,相信你已經(jīng)掌握的很好了。那么本節(jié)我們就繼續(xù)深入源碼和原理,來一起看看任務提交到線程之后的源碼執(zhí)行流程具體是什么樣子的。

          ? 按照慣例,我們先看一張任務執(zhí)行的完整流程如圖1所示。


          圖1

          ?我們根據(jù)這張圖中的主要節(jié)點,一步一步結(jié)合源碼來分析一下。

          任務提交

          ????首先是任務提交,線程池任務提交有兩種方式,execute()submit()。首先看一下execute方法的源碼。我們發(fā)現(xiàn)它接收的入?yún)⑹且粋€Runnable類型。我們按照代碼截圖中的序號依次分析,具體分析內(nèi)容如圖2所示。

          2

          ???????? [1]首先,一進方法就先對command進行校驗,如果command為空就直接拋出NPE異常;

          ???????? [2,3]然后判斷一下線程池中的線程個數(shù)是否小于corePoolSize,如果滿足條件就調(diào)用addWorker(command, true)方法區(qū)執(zhí)行任務。這個方法實際上最終就是開啟了新的線程去執(zhí)行任務。

          ???????? [4]如果說線程池處于RUNNING狀態(tài),也就是isRunning(c)返回true,那么就將任務添加到阻塞隊列。也就是執(zhí)行workQueue.offer(commmand)。

          ???????? [5,6]為了確保能夠準確的將任務添加成功,線程池在這里做了二次校驗。這里是因為,如果將任務添加到線程池之后,有可能線程池狀態(tài)已經(jīng)變化了,所以要校驗一下,看看當前的線程池狀態(tài)還是不是RUNNING。

          ???????? 如果線程池狀態(tài)不是RUNNING了,就把任務從任務隊列中刪除,也就是remove(command),然后就執(zhí)行拒絕策略,也就是調(diào)用reject(command)方法。

          ???????? [7]如果說線程池狀態(tài)確實是RUNNING,也就是二次校驗通過,那么就判斷一下線程池里是否還有線程,通過WorkerCountOf(recheck) == 0來判斷,如果返回true了,就說明當前線程池中是空的,沒有線程。怎么辦呢?其實很好理解,既然沒有就添加一個就完事兒了,調(diào)用一下addWorker往線程集合里增加一個線程,如圖3所示。

          3

          ???????? 看到了吧,實際上所謂的Workers就是一個HashSet,而Worker則本質(zhì)上是一個Runnable,但是它實現(xiàn)了AQS,代碼比較復雜,也很精巧,我們稍安勿躁繼續(xù)慢慢的分析。

          ???????? [8]在執(zhí)行[4]的時候?qū)θ蝿者M行添加,如果添加失敗就說明任務隊列已經(jīng)滿了,那么只要線程池中的線程數(shù)小于maximumPoolSize繼續(xù)新開線程來執(zhí)行任務。

          ???????? 如果線程數(shù)要超過maximumPoolSize了,就需要執(zhí)行拒絕策略了。

          ??? 到這里我們對提交任務的代碼就分析結(jié)束,這些步驟其實就是本文一開始的圖1,我們看一次加深下印象。


          Worker線程獲取任務執(zhí)行流程

          通過上面的討論以及我們在之前文章中的鋪墊,大家應該都知道了任務是被線程池中的工作線程(也就是Worker線程)執(zhí)行的了。那我們就來看看,Worker線程是如何獲取任務然后執(zhí)行的。

          上面的流程中,我們已經(jīng)知道了線程池是通過執(zhí)行addWorker方法來增加Worker線程的,實際上Worker線程就是在這個階段被啟動的,具體我們來看看代碼,如圖4所示。

          4

          這段代碼是截取的addWorker方法,注意我們用紅框圈住的三個地方,這幾個地方是需要重點關(guān)注的。

          首先第一個位置,我們聲明了一個Worker線程,并把它持有的thread成員變量的引用,賦值給final修飾的Thread t臨時變量,然后判斷t是否是alive狀態(tài)。如果是,那么就拋出一個IllegalThreadStateException異常,也就不用啟動了,因為既然已經(jīng)啟動了,就無需在啟動了啊。

          第二個位置,將這個新的Worker線程添加到工作線程集合中,通過上文的分析我們知道它是一個HashSet。并設置WorkerAdded狀態(tài)變量為true

          第三個位置,校驗WorkerAdded狀態(tài)變量為true成立,就通過start()方法啟動工作線程,其實最終啟動的是Worker內(nèi)部持有的Thread成員變量。通過Worker的構(gòu)造方法就能知曉其中的奧義,如圖5所示。

          5

          ???????? 到這里,我們其實已經(jīng)知道了,最終調(diào)度任務的Worker工作線程,通過構(gòu)造方法我們已經(jīng)知道Worker本質(zhì)上實現(xiàn)了Runnable接口,那么就能夠被Thread啟動,這個想必大家都知道對吧。

          6

          ???????? 知道了這個,就好辦了,我們回過頭去研究研究,Workerrun方法。

          ???????? 為什么要研究run方法呢?兄弟,既然我們都說了WorkerRunnable的實現(xiàn),那最終線程啟動之后,調(diào)度的就是它的run方法邏輯啊,也就是說,Worker肯定是實現(xiàn)了run方法了。代碼是不會說謊的,如圖7所示。

          7

          ???????? 怎么樣,Worker線程的確是實現(xiàn)了run方法了,核心邏輯都在runWorker方法里,那我們就繼續(xù)深入。

          8

          ???????? 看到這么一大段代碼,想必有的兄弟又開始發(fā)慌了。不慌,這里分享一個讀源碼的小技巧,“抓大放小,分析重點”。我們看源碼重在找到重點,至于其他的細節(jié),慢慢看,甚至于說,你不去深究也無傷大雅,如果鉆牛角尖,非得弄清每行代碼,一方面時間上不現(xiàn)實,一方面肯定會很辛苦,而且得不償失啊。

          ???????? 從這個“抓大放小,分析重點”的原則出發(fā),我們就關(guān)注紅框圈住的兩行重點代碼:

          ???????? 第一個位置,我們把Workertask引用賦值給了Runnable局部變量;

          第二個位置要額外關(guān)注,有的兄弟不知道為什么隊列中的任務會被工作線程執(zhí)行,其實就是這句代碼在起作用。通過調(diào)用task = getTask()方法,我截取了getTask方法的核心代碼,我們能夠直觀的看到實際上最終是調(diào)用的workQueue.take()方法取出了隊列中的任務,本質(zhì)上就是生產(chǎn)者-消費者模型。

          9

          ???????? 第三個位置,執(zhí)行taskrun方法,也就是用戶提交的真正的業(yè)務邏輯。

          ???????? 你可能想問了,明明這里執(zhí)行的是Runnablerun方法,那工作線程到哪里去了?

          ???????? 兄弟,這個問題問的有水平!

          ???????? 我們在上面的分析中,提到了addWorker這個方法,里面就對工作線程進行了start調(diào)用,其實這里就啟動了工作線程,如圖10所示。

          10

          ???????? 我們通過一步一步走讀的方式,抽絲剝繭,把Worker線程獲取任務并執(zhí)行的流程全面的展示了出來。通過圖11來展示一下這個過程便于加深理解。

          ?

          11

          ???????? 通過圖11表示的流程,我們主要記住一個結(jié)論:當我們需要向線程池提交任務的時候,通過調(diào)用execute()傳進去的任務(Runnable或者Callable實現(xiàn)),最終會通過Worker的構(gòu)造方法傳遞到Worker內(nèi)部,這樣當start啟動的以后真正執(zhí)行的就是Worker中的Runnable,也就是用戶提交的Runnable。

          也許你會說,聽起來有些復雜???不好意思,代碼閱讀的過程本身確實不是一個簡單的事情,但是我們可以把原理講的通俗易懂。因為原理本來就是可以用很簡單很直白的話講清楚的東西。

          代碼實現(xiàn)可以很復雜,但是原理越能讓人理解越說明原理是普適的。如果你對代碼不能很好的理解,就把這張圖記住,然后再回去看源碼,相信你會把握住主脈絡。畢竟俗話說,撿了芝麻,丟了西瓜。我們搞技術(shù)的可切忌這樣做,抓大放小才能出奇制勝啊。

          Worker線程什么時候退出

          ?????? 分析完Worker線程的執(zhí)行,我們再趁熱打鐵看看Worker線程是什么時候退出的。

          ??? 事實上,線程池中的線程銷毀,是要依賴JVM來實現(xiàn)自動回收的。而線程池自身,會根據(jù)當前線程池的狀態(tài)來維持一定數(shù)量的線程引用,防止該部分的線程被JVM回收掉。

          ??? 如果線程池決定了哪些線程要被回收,那么就將他們的引用消除即可。

          ??? 我們在之前的學習中知道,Worker線程一旦被創(chuàng)建好了,就會持續(xù)的輪詢獲取任務去執(zhí)行。

          ??? 對于核心線程來說,他們可以無限制的等待著任務被獲取并執(zhí)行,而非核心的線程則是在有限的時間內(nèi)獲取任務,一旦Worker無法獲取到任務,也就是要獲取的任務為空,循環(huán)就會結(jié)束,Worker自己就會主動的去除掉在線程池中的應用,進而被回收掉并退出。

          ??? 從代碼角度看一下這個過程是在什么時候發(fā)生的吧,如圖12所示。

          圖12

          ??? 我們還是回到runWorker方法,發(fā)現(xiàn)在try塊中有個while循環(huán),循環(huán)停止的條件就是要獲取的任務為空以及通過getTask獲取不到任務了,最終會進到finally塊執(zhí)行收尾邏輯,如圖13所示。

          圖13

          ??? 首先[1]部分的代碼是說,在執(zhí)行線程退出之前,線程池會先統(tǒng)計池子里完成任務的數(shù)量,然后通過workers.remove(w)把Worker移除掉。要注意的是在統(tǒng)計之前加了全局鎖,保證統(tǒng)計的準確性。

          ??? [2]部分的代碼是說,如果當前線程池狀態(tài)是SHUTDOWN狀態(tài)并且工作隊列已經(jīng)為空,或者當前線程池已經(jīng)是STOP狀態(tài),或者說當前線程池中沒有活動的線程,則嘗試對線程池狀態(tài)設置為TERMINATED。

          ??? [3]部分是說,最后還是得判斷一下線程池里面的實際線程數(shù)是否小于核心的線程個數(shù),如果是的話,就得增加線程。這里的目的是保證線程池中的核心線程數(shù)量不變。

          ??? 怎么樣,是不是覺得線程池沒有那么可怕了?我們繼續(xù)趁熱打鐵,通過一張圖來總結(jié)一下Worker線程退出的邏輯,如圖14所示。

          圖14

          ??? 面試中如果遇到這個問題,那么就把這張圖的過程說出來就可以了,你可以說,我是在閱讀并理解了源碼的基礎(chǔ)上總結(jié)出來的。相信你一定能夠得到面試官的青睞。

          ??? 到這里,本節(jié)的文章又告一段落,感謝你一直看到現(xiàn)在。

          ??? 確實這篇文章相對比較硬核,我們畢竟是需要通過閱讀源碼的方式去加深理解。通過對本節(jié)的學習,相信你對線程池提交任務的過程、Worker線程獲取任務并執(zhí)行的過程以及Worker線程退出過程都有了源碼級的深入了解。同時筆者閱讀的源碼“抓大放小,分析重點”的方法,相信你也get到精髓了,希望你能夠利用這種思路,征服源碼,收獲更多。

          ??? 在之后的章節(jié)中,我們將一同從實戰(zhàn)角度出發(fā),研究如何自定義線程池滿足不同的業(yè)務場景,敬請期待,


          瀏覽 144
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

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

          手機掃一掃分享

          分享
          舉報
          <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>
                  成人网站mv在线 | 中文字幕一区精品 | 人体模特小妮流水 | 中文字幕欧美高清 | 大香蕉97视频 |