<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                企業??AI智能體構建引擎,智能編排和調試,一鍵部署,支持知識庫和私有化部署方案 廣告
                ## 39 經驗總結:不同場景,如何使用線程池 ## 引導語 ThreadPoolExecutor 初始化時,主要有如下幾個參數: ``` public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) { ``` 大家對這幾個參數應該都很熟悉了,雖然參數很少,但實際工作中卻有很多門道,大多數的問題主要集中在線程大小的設置,隊列大小的設置兩方面上,接下來我們一起看看工作中,如何初始化 ThreadPoolExecutor。 ### 1 coreSize == maxSize 我相信很多人都看過,或自己寫過這樣的代碼: ``` ThreadPoolExecutor executor = new ThreadPoolExecutor(10, 10, 600000L, TimeUnit.DAYS, new LinkedBlockingQueue()); ``` 這行代碼主要展示了在初始化 ThreadPoolExecutor 的時候,coreSize 和 maxSize 是相等的,這樣設置的話,隨著請求的不斷增加,會是這樣的現象: 1. 請求數 < coreSize 時,新增線程; 2. 請求數 >= coreSize && 隊列不滿時,添加任務入隊; 3. 隊列滿時,此時因為 coreSize 和 maxSize 相等,任務會被直接拒絕。 這么寫的最大目的:是想讓線程一下子增加到 maxSize,并且不要回收線程,防止線程回收,避免不斷增加回收的損耗,一般來說業務流量都有波峰低谷,在流量低谷時,線程不會被回收;流量波峰時,maxSize 的線程可以應對波峰,不需要慢慢初始化到 maxSize 的過程。 這樣設置有兩個前提條件: 1. allowCoreThreadTimeOut 我們采取默認 false,而不會主動設置成 true,allowCoreThreadTimeOut 是 false 的話,當線程空閑時,就不會回收核心線程; 2. keepAliveTime 和 TimeUnit 我們都會設置很大,這樣線程空閑的時間就很長,線程就不會輕易的被回收。 我們現在機器的資源都是很充足的,我們不用去擔心線程空閑會浪費機器的資源,所以這種寫法目前是很常見的。 ### 2 maxSize 無界 + SynchronousQueue 在線程池選擇隊列時,我們也會看到有同學選擇 SynchronousQueue,SynchronousQueue 我們在 《SynchronousQueue 源碼解析》章節有說過,其內部有堆棧和隊列兩種形式,默認是堆棧的形式,其內部是沒有存儲的容器的,放元素和拿元素是一一對應的,比如我使用 put 方法放元素,如果此時沒有對應的 take 操作的話,put 操作就會阻塞,需要有線程過來執行 take 操作后,put 操作才會返回。 基于此特點,如果要使用 SynchronousQueue 的話,我們需要盡量將 maxSize 設置大一點,這樣就可以接受更多的請求。 假設我們設置 maxSize 是 10 的話,選擇 SynchronousQueue 隊列,假設所有請求都執行 put 操作,沒有請求執行 take 操作,前 10 個 put 請求會消耗 10 個線程,都阻塞在 put 操作上,第 11 個請求過來后,請求就會被拒絕,所以我們才說盡量把 maxSize 設置大一點,防止請求被拒絕。 maxSize 無界 + SynchronousQueue 這樣的組合方式優缺點都很明顯: 優點:當任務被消費時,才會返回,這樣請求就能夠知道當前請求是已經在被消費了,如果是其他的隊列的話,我們只知道任務已經被提交成功了,但無法知道當前任務是在被消費中,還是正在隊列中堆積。 缺點: 1. 比較消耗資源,大量請求到來時,我們會新建大量的線程來處理請求; 2. 如果請求的量難以預估的話,maxSize 的大小也很難設置。 ### 3 maxSize 有界 + Queue 無界 在一些對實時性要求不大,但流量忽高忽低的場景下,可以使用 maxSize 有界 + Queue 無界的組合方式。 比如我們設置 maxSize 為 20,Queue 選擇默認構造器的 LinkedBlockingQueue,這樣做的優缺點如下: 優點: 1. 電腦 cpu 固定的情況下,每秒能同時工作的線程數是有限的,此時開很多的線程其實也是浪費,還不如把這些請求放到隊列中去等待,這樣可以減少線程之間的 CPU 的競爭; 2. LinkedBlockingQueue 默認構造器構造出來的鏈表的最大容量是 Integer 的最大值,非常適合流量忽高忽低的場景,當流量高峰時,大量的請求被阻塞在隊列中,讓有限的線程可以慢慢消費。 缺點:流量高峰時,大量的請求被阻塞在隊列中,對于請求的實時性難以保證,所以當對請求的實時性要求較高的場景,不能使用該組合。 ### 4 maxSize 有界 + Queue 有界 這種組合是對 3 缺點的補充,我們把隊列從無界修改成有界,只要排隊的任務在要求的時間內,能夠完成任務即可。 這種組合需要我們把線程和隊列的大小進行配合計算,保證大多數請求都可以在要求的時間內,有響應返回。 ### 5 keepAliveTime 設置無窮大 有些場景下我們不想讓空閑的線程被回收,于是就把 keepAliveTime 設置成 0,實際上這種設置是錯誤的,當我們把 keepAliveTime 設置成 0 時,線程使用 poll 方法在隊列上進行超時阻塞時,會立馬返回 null,也就是空閑線程會立馬被回收。 所以如果我們想要空閑的線程不被回收,我們可以設置 keepAliveTime 為無窮大值,并且設置 TimeUnit 為時間的大單位,比如我們設置 keepAliveTime 為 365,TimeUnit 為 TimeUnit.DAYS,意思是線程空閑 1 年內都不會被回收。 在實際的工作中,機器的內存一般都夠大,我們合理設置 maxSize 后,即使線程空閑,我們也不希望線程被回收,我們常常也會設置 keepAliveTime 為無窮大。 ### 6 線程池的公用和獨立 在實際工作中,某一個業務下的所有場景,我們都不會公用一個線程池,一般有以下幾個原則: 1. 查詢和寫入不公用線程池,互聯網應用一般來說,查詢量遠遠大于寫入的量,如果查詢和寫入都要走線程池的話,我們一定不要公用線程池,也就是說查詢走查詢的線程池,寫入走寫入的線程池,如果公用的話,當查詢量很大時,寫入的請求可能會到隊列中去排隊,無法及時被處理; 2. 多個寫入業務場景看情況是否需要公用線程池,原則上來說,每個業務場景都獨自使用自己的線程池,絕不共用,這樣在業務治理、限流、熔斷方面都比較容易,一旦多個業務場景公用線程池,可能就會造成業務場景之間的互相影響,現在的機器內存都很大,每個寫入業務場景獨立使用自己的線程池也是比較合理的; 3. 多個查詢業務場景是可以公用線程池的,查詢的請求一般來說有幾個特點:查詢的場景多、rt 時間短、查詢的量比較大,如果給每個查詢場景都弄一個單獨的線程池的話,第一個比較耗資源,第二個很難定義線程池中線程和隊列的大小,比較復雜,所以多個相似的查詢業務場景是可以公用線程池的。 ### 7 如何算線程大小和隊列大小 在實際的工作中,我們使用線程池時,需要慎重考慮線程的大小和隊列的大小,主要從幾個方面入手: 1. 根據業務進行考慮,初始化線程池時,我們需要考慮所有業務涉及的線程池,如果目前所有的業務同時都有很大流量,那么在對于當前業務設置線程池時,我們盡量把線程大小、隊列大小都設置小,如果所有業務基本上都不會同時有流量,那么就可以稍微設置大一點; 2. 根據業務的實時性要求,如果實時性要求高的話,我們把隊列設置小一點,coreSize == maxSize,并且設置 maxSize 大一點,如果實時性要求低的話,就可以把隊列設置大一點。 假設現在機器上某一時間段只會運行一種業務,業務的實時性要求較高,每個請求的平均 rt 是 200ms,請求超時時間是 2000ms,機器是 4 核 CPU,內存 16G,一臺機器的 qps 是 100,這時候我們可以模擬一下如何設置: 1. 4 核 CPU,假設 CPU 能夠跑滿,每個請求的 rt 是 200ms,就是 200 ms 能執行 4 條請求,2000ms 內能執行 2000/200 * 4 = 40 條請求; 2. 200 ms 能執行 4 條請求,實際上 4 核 CPU 的性能遠遠高于這個,我們可以拍腦袋加 10 條,也就是說 2000ms 內預估能夠執行 50 條; 3. 一臺機器的 qps 是 100,此時我們計算一臺機器 2 秒內最多處理 50 條請求,所以此時如果不進行 rt 優化的話,我們需要加至少一臺機器。 線程池可以大概這么設置: ``` ThreadPoolExecutor executor = new ThreadPoolExecutor(15, 15, 365L, TimeUnit.DAYS, new LinkedBlockingQueue(35)); ``` 線程數最大為 15,隊列最大為 35,這樣機器差不多可以在 2000ms 內處理最大的請求 50 條,當然根據你機器的性能和實時性要求,你可以調整線程數和隊列的大小占比,只要總和小于 50 即可。 以上只是很粗糙的設置,在實際的工作中,還需要根據實際情況不斷的觀察和調整。 ### 總結 線程池設置非常重要,我們盡量少用 Executors 類提供的各種初始化線程池的方法,多根據業務的量,實時性要求來計算機器的預估承載能力,設置預估的線程和隊列大小,并且根據實時請求不斷的調整線程池的大小值。
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看