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

          線程池大小 + 線程數(shù)量到底設(shè)置多少?

          共 6455字,需瀏覽 13分鐘

           ·

          2021-05-21 19:46

          往期熱門文章:

          1、面試官問:數(shù)據(jù)庫 delete 表數(shù)據(jù),磁盤空間還是被一直占用,為什么?
          2、巧用 Stream API 優(yōu)化 Java 代碼
          3、最牛逼的故障診斷工具!秒級定位線上問題
          4、新技能 MyBatis 千萬數(shù)據(jù)表,快速分頁!
          5、常見的SQL面試題:經(jīng)典50例
          來源 | juejin.cn/post/6948034657321484318

          可能很多人都看到過一個線程數(shù)設(shè)置的理論:

          • CPU 密集型的程序 - 核心數(shù) + 1
          • I/O 密集型的程序 - 核心數(shù) * 2

          不會吧,不會吧,真的有人按照這個理論規(guī)劃線程數(shù)?

          線程數(shù)和CPU利用率的小測試

          拋開一些操作系統(tǒng),計算機原理不談,說一個基本的理論(不用糾結(jié)是否嚴(yán)謹(jǐn),只為好理解):一個CPU核心,單位時間內(nèi)只能執(zhí)行一個線程的指令 ** 那么理論上,我一個線程只需要不停的執(zhí)行指令,就可以跑滿一個核心的利用率。

          來寫個死循環(huán)空跑的例子驗證一下:

          測試環(huán)境:AMD Ryzen 5 3600, 6 - Core, 12 - Threads

          public class CPUUtilizationTest {
              public static void main(String[] args) {
                  //死循環(huán),什么都不做
                  while (true){
                  }
              }
          }

          運行這個例子后,來看看現(xiàn)在CPU的利用率:

          從圖上可以看到,我的3號核心利用率已經(jīng)被跑滿了

          那基于上面的理論,我多開幾個線程試試呢?

          public class CPUUtilizationTest {
           public static void main(String[] args) {

            for (int j = 0; j < 6; j++) {
             new Thread(new Runnable() {
              @Override
              public void run() {
               while (true){
               }
              }
             }).start();
            }
           }
          }

          此時再看CPU利用率,1/2/5/7/9/11 幾個核心的利用率已經(jīng)被跑滿:

          那如果開12個線程呢,是不是會把所有核心的利用率都跑滿?答案一定是會的:

          如果此時我把上面例子的線程數(shù)繼續(xù)增加到24個線程,會出現(xiàn)什么結(jié)果呢?

          從上圖可以看到,CPU利用率和上一步一樣,還是所有核心100%,不過此時負(fù)載已經(jīng)從11.x增加到了22.x(load average解釋參考scoutapm.com/blog/unders…),說明此時CPU更繁忙,線程的任務(wù)無法及時執(zhí)行。

          現(xiàn)代CPU基本都是多核心的,比如我這里測試用的AMD 3600,6核心12線程(超線程),我們可以簡單的認(rèn)為它就是12核心CPU。那么我這個CPU就可以同時做12件事,互不打擾。

          如果要執(zhí)行的線程大于核心數(shù),那么就需要通過操作系統(tǒng)的調(diào)度了。操作系統(tǒng)給每個線程分配CPU時間片資源,然后不停的切換,從而實現(xiàn)“并行”執(zhí)行的效果。

          但是這樣真的更快嗎?從上面的例子可以看出,一個線程 就可以把一個核心 的利用率跑滿。如果每個線程都很“霸道”,不停的執(zhí)行指令,不給CPU空閑的時間,并且同時執(zhí)行的線程數(shù)大于CPU的核心數(shù),就會導(dǎo)致操作系統(tǒng)更頻繁的執(zhí)行切換線程執(zhí)行 ,以確保每個線程都可以得到執(zhí)行。

          不過切換是有代價的,每次切換會伴隨著寄存器數(shù)據(jù)更新,內(nèi)存頁表更新等操作 。雖然一次切換的代價和I/O操作比起來微不足道,但如果線程過多,線程切換的過于頻繁,甚至在單位時間內(nèi)切換的耗時已經(jīng)大于程序執(zhí)行的時間,就會導(dǎo)致CPU資源過多的浪費在上下文切換上,而不是在執(zhí)行程序,得不償失。

          上面死循環(huán)空跑的例子,有點過于極端了,正常情況下不太可能有這種程序。

          大多程序在運行時都會有一些 I/O操作,可能是讀寫文件,網(wǎng)絡(luò)收發(fā)報文等,這些 I/O 操作在進行時時需要等待反饋的。比如網(wǎng)絡(luò)讀寫時,需要等待報文發(fā)送或者接收到,在這個等待過程中,線程是等待狀態(tài),CPU沒有工作。此時操作系統(tǒng)就會調(diào)度CPU去執(zhí)行其他線程的指令,這樣就完美利用了CPU這段空閑期,提高了CPU的利用率。

          上面的例子中,程序不停的循環(huán)什么都不做,CPU要不停的執(zhí)行指令,幾乎沒有啥空閑的時間。如果插入一段I/O操作呢,I/O 操作期間 CPU是空閑狀態(tài),CPU的利用率會怎么樣呢?先看看單線程下的結(jié)果:

          public class CPUUtilizationTest {
           public static void main(String[] args) throws InterruptedException {

            for (int n = 0; n < 1; n++) {
             new Thread(new Runnable() {
              @Override
              public void run() {
               while (true){
                                  //每次空循環(huán) 1億 次后,sleep 50ms,模擬 I/O等待、切換
                for (int i = 0; i < 100_000_000l; i++) {
                }
                try {
                 Thread.sleep(50);
                }
                catch (InterruptedException e) {
                 e.printStackTrace();
                }
               }
              }
             }).start();
            }
           }
          }

          哇,唯一有利用率的9號核心,利用率也才50%,和前面沒有sleep的100%相比,已經(jīng)低了一半了。現(xiàn)在把線程數(shù)調(diào)整到12個看看:

          單個核心的利用率60左右,和剛才的單線程結(jié)果差距不大,還沒有把CPU利用率跑滿,現(xiàn)在將線程數(shù)增加到18:

          此時單核心利用率,已經(jīng)接近100%了。由此可見,當(dāng)線程中有 I/O 等操作不占用CPU資源時,操作系統(tǒng)可以調(diào)度CPU可以同時執(zhí)行更多的線程。

          現(xiàn)在將I/O事件的頻率調(diào)高看看呢,把循環(huán)次數(shù)減到一半,50_000_000,同樣是18個線程:

          此時每個核心的利用率,大概只有70%左右了。

          線程數(shù)和CPU利用率的小總結(jié)

          上面的例子,只是輔助,為了更好的理解線程數(shù)/程序行為/CPU狀態(tài)的關(guān)系,來簡單總結(jié)一下:

          1. 一個極端的線程(不停執(zhí)行“計算”型操作時),就可以把單個核心的利用率跑滿,多核心CPU最多只能同時執(zhí)行等于核心數(shù)的“極端”線程數(shù)
          2. 如果每個線程都這么“極端”,且同時執(zhí)行的線程數(shù)超過核心數(shù),會導(dǎo)致不必要的切換,造成負(fù)載過高,只會讓執(zhí)行更慢
          3. I/O 等暫停類操作時,CPU處于空閑狀態(tài),操作系統(tǒng)調(diào)度CPU執(zhí)行其他線程,可以提高CPU利用率,同時執(zhí)行更多的線程
          4. I/O 事件的頻率頻率越高,或者等待/暫停時間越長,CPU的空閑時間也就更長,利用率越低,操作系統(tǒng)可以調(diào)度CPU執(zhí)行更多的線程

          線程數(shù)規(guī)劃的公式

          前面的鋪墊,都是為了幫助理解,現(xiàn)在來看看書本上的定義。《Java 并發(fā)編程實戰(zhàn)》介紹了一個線程數(shù)計算的公式:

          如果希望程序跑到CPU的目標(biāo)利用率,需要的線程數(shù)公式為:

          公式很清晰,現(xiàn)在來帶入上面的例子試試看:

          如果我期望目標(biāo)利用率為90%(多核90),那么需要的線程數(shù)為:

          現(xiàn)在把線程數(shù)調(diào)到22,看看結(jié)果:

          現(xiàn)在CPU利用率大概80+,和預(yù)期比較接近了,由于線程數(shù)過多,還有些上下文切換的開銷,再加上測試用例不夠嚴(yán)謹(jǐn),所以實際利用率低一些也正常。

          把公式變個形,還可以通過線程數(shù)來計算CPU利用率:

          雖然公式很好,但在真實的程序中,一般很難獲得準(zhǔn)確的等待時間和計算時間,因為程序很復(fù)雜,不只是“計算” 。一段代碼中會有很多的內(nèi)存讀寫,計算,I/O 等復(fù)合操作,精確的獲取這兩個指標(biāo)很難,所以光靠公式計算線程數(shù)過于理想化。

          真實程序中的線程數(shù)

          那么在實際的程序中,或者說一些Java的業(yè)務(wù)系統(tǒng)中,線程數(shù)(線程池大小)規(guī)劃多少合適呢?

          先說結(jié)論:沒有固定答案,先設(shè)定預(yù)期,比如我期望的CPU利用率在多少,負(fù)載在多少,GC頻率多少之類的指標(biāo)后,再通過測試不斷的調(diào)整到一個合理的線程數(shù)

          比如一個普通的,SpringBoot 為基礎(chǔ)的業(yè)務(wù)系統(tǒng),默認(rèn)Tomcat容器+HikariCP連接池+G1回收器,如果此時項目中也需要一個業(yè)務(wù)場景的多線程(或者線程池)來異步/并行執(zhí)行業(yè)務(wù)流程。

          此時我按照上面的公式來規(guī)劃線程數(shù)的話,誤差一定會很大。因為此時這臺主機上,已經(jīng)有很多運行中的線程了,Tomcat有自己的線程池,HikariCP也有自己的后臺線程,JVM也有一些編譯的線程,連G1都有自己的后臺線程。這些線程也是運行在當(dāng)前進程、當(dāng)前主機上的,也會占用CPU的資源。

          所以受環(huán)境干擾下,單靠公式很難準(zhǔn)確的規(guī)劃線程數(shù),一定要通過測試來驗證。

          流程一般是這樣:

          1. 分析當(dāng)前主機上,有沒有其他進程干擾

          2. 分析當(dāng)前JVM進程上,有沒有其他運行中或可能運行的線程

          3. 設(shè)定目標(biāo)

            1. 目標(biāo)CPU利用率 - 我最高能容忍我的CPU飆到多少?

            2. 目標(biāo)GC頻率/暫停時間 - 多線程執(zhí)行后,GC頻率會增高,最大能容忍到什么頻率,每次暫停時間多少?

            3. 執(zhí)行效率 - 比如批處理時,我單位時間內(nèi)要開多少線程才能及時處理完畢

            4. ……

          4. 梳理鏈路關(guān)鍵點,是否有卡脖子的點,因為如果線程數(shù)過多,鏈路上某些節(jié)點資源有限可能會導(dǎo)致大量的線程在等待資源(比如三方接口限流,連接池數(shù)量有限,中間件壓力過大無法支撐等)

          5. 不斷的增加/減少線程數(shù)來測試,按最高的要求去測試,最終獲得一個“滿足要求”的線程數(shù)**

          而且而且而且!不同場景下的線程數(shù)理念也有所不同:

          1. Tomcat中的maxThreads,在Blocking I/O和No-Blocking I/O下就不一樣
          2. Dubbo 默認(rèn)還是單連接呢,也有I/O線程(池)和業(yè)務(wù)線程(池)的區(qū)分,I/O線程一般不是瓶頸,所以不必太多,但業(yè)務(wù)線程很容易稱為瓶頸
          3. Redis 6.0以后也是多線程了,不過它只是I/O 多線程,“業(yè)務(wù)”處理還是單線程

          所以,不要糾結(jié)設(shè)置多少線程了。沒有標(biāo)準(zhǔn)答案,一定要結(jié)合場景,帶著目標(biāo),通過測試去找到一個最合適的線程數(shù)。

          可能還有同學(xué)可能會有疑問:“我們系統(tǒng)也沒啥壓力,不需要那么合適的線程數(shù),只是一個簡單的異步場景,不影響系統(tǒng)其他功能就可以”

          很正常,很多的內(nèi)部業(yè)務(wù)系統(tǒng),并不需要啥性能,穩(wěn)定好用符合需求就可以了,那么我的推薦的線程數(shù)是:CPU核心數(shù)

          附錄

          Java 獲取CPU核心數(shù)

          Runtime.getRuntime().availableProcessors()//獲取邏輯核心數(shù),如6核心12線程,那么返回的是12

          Linux 獲取CPU核心數(shù)

          # 總核數(shù) = 物理CPU個數(shù) X 每顆物理CPU的核數(shù)
          # 總邏輯CPU數(shù) = 物理CPU個數(shù) X 每顆物理CPU的核數(shù) X 超線程數(shù)

          # 查看物理CPU個數(shù)
          cat /proc/cpuinfo| grep "physical id"| sort| uniq| wc -l

          # 查看每個物理CPU中core的個數(shù)(即核數(shù))
          cat /proc/cpuinfo| grep "cpu cores"| uniq

          # 查看邏輯CPU的個數(shù)
          cat /proc/cpuinfo| grep "processor"| wc -l

          最近熱文閱讀:

          1、從 0 到 1 手把手教你制作酷炫可視化大屏
          2、巧用 Stream API 優(yōu)化 Java 代碼
          3、最牛逼的故障診斷工具!秒級定位線上問題
          4、一次線上 JVM 調(diào)優(yōu)實踐,F(xiàn)ullGC 40 次/天到 10 天一次的優(yōu)化過程
          5、新技能 MyBatis 千萬數(shù)據(jù)表,快速分頁!
          6、常見的SQL面試題:經(jīng)典50例
          7、事務(wù)注解 @Transactional 失效的3種場景及解決辦法
          8、看看人家SpringBoot的全局異常處理多么優(yōu)雅...
          9、代碼總是被嫌棄寫的太爛?裝上這個IDEA插件再試試!
          10、60個相見恨晚的神器工具!
          關(guān)注公眾號,你想要的Java都在這里

          瀏覽 38
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

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

          手機掃一掃分享

          分享
          舉報
          <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>
                  AV天堂资源 | 91香蕉网站在线下载 | 18禁黄无码 | 亚洲国产日韩在线一区 | 超碰人人超碰 |