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

          線程池的幾個(gè)靈魂拷問(wèn)?

          共 9952字,需瀏覽 20分鐘

           ·

          2021-07-17 08:20


          相關(guān)閱讀

          300本計(jì)算機(jī)編程的經(jīng)典書籍下載

          AI全套:Python3+TensorFlow打造人臉識(shí)別智能小程序

          最新人工智能資料-Google工程師親授 Tensorflow-入門到進(jìn)階

          Java架構(gòu)全階段七期完整

          黑馬頭條項(xiàng)目 - Java Springboot2.0(視頻、資料、代碼和講義)14天完整版

          Spring核心編程思想

          作者:千淘萬(wàn)漉
          鏈接:https://www.jianshu.com/p/e39fb9030722

          之前的博客里有寫過(guò)一點(diǎn)線程池,但是只是蜻蜓點(diǎn)水式的談了一下,恰巧前段時(shí)間在工作中有了線程池的使用經(jīng)驗(yàn),而且線程池的優(yōu)化又是一個(gè)比較有挑戰(zhàn)的難題,所以這里借著實(shí)戰(zhàn)經(jīng)驗(yàn)結(jié)合原理來(lái)一篇線程池的總結(jié)文章。


          為什么要用線程池?

          線程池解決的核心問(wèn)題就是資源管理問(wèn)題。在并發(fā)環(huán)境下,系統(tǒng)不能夠確定在任意時(shí)刻中,有多少任務(wù)需要執(zhí)行,有多少資源需要投入。這種不確定性將帶來(lái)以下若干問(wèn)題:

          • 頻繁申請(qǐng)/銷毀資源和調(diào)度資源,將帶來(lái)額外的消耗,可能會(huì)非常巨大。

          • 對(duì)資源無(wú)限申請(qǐng)缺少抑制手段,易引發(fā)系統(tǒng)資源耗盡的風(fēng)險(xiǎn)。

          • 系統(tǒng)無(wú)法合理管理內(nèi)部的資源分布,會(huì)降低系統(tǒng)的穩(wěn)定性。

          為解決資源分配這個(gè)問(wèn)題,線程池采用了“池化”(Pooling)思想。池化,顧名思義,就是將資源統(tǒng)一在一起管理的一種思想。

          線程池的核心參數(shù)

          Java中的線程池核心實(shí)現(xiàn)類是ThreadPoolExecutor,ThreadPoolExecutor實(shí)現(xiàn)的頂層接口是Executor,頂層接口Executor提供了一種思想:將任務(wù)提交和任務(wù)執(zhí)行進(jìn)行解耦。用戶無(wú)需關(guān)注如何創(chuàng)建線程,如何調(diào)度線程來(lái)執(zhí)行任務(wù),用戶只需提供Runnable對(duì)象,將任務(wù)的運(yùn)行邏輯提交到執(zhí)行器(Executor)中,由Executor框架完成線程的調(diào)配和任務(wù)的執(zhí)行部分。ThreadPoolExecutor,主要構(gòu)造方法:

          public ThreadPoolExecutor(int corePoolSize,
                                        int maximumPoolSize,
                                        long keepAliveTime,
                                        TimeUnit unit,
                                        BlockingQueue<Runnable> workQueue,
                                        ThreadFactory threadFactory,
                                        RejectedExecutionHandler handler)

          corePoolSize:核心線程大小,當(dāng)提交一個(gè)任務(wù)到線程池時(shí),線程池會(huì)創(chuàng)建一個(gè)線程來(lái)執(zhí)行任務(wù),即使有其他空閑線程可以處理任務(wù)也會(huì)創(chuàng)新線程,等到工作的線程數(shù)大于核心線程數(shù)時(shí)就不會(huì)在創(chuàng)建了。如果調(diào)用了線程池的prestartAllCoreThreads方法,線程池會(huì)提前把核心線程都創(chuàng)造好,并啟動(dòng)

          maximumPoolSize:線程池允許創(chuàng)建的最大線程數(shù)。如果隊(duì)列滿了,并且以創(chuàng)建的線程數(shù)小于最大線程數(shù),則線程池會(huì)再創(chuàng)建新的線程執(zhí)行任務(wù)。如果我們使用了無(wú)界隊(duì)列,那么所有的任務(wù)會(huì)加入隊(duì)列,這個(gè)參數(shù)就沒(méi)有什么效果了

          keepAliveTime:線程池的工作線程空閑后,保持存活的時(shí)間。如果沒(méi)有任務(wù)處理了,有些線程會(huì)空閑,空閑的時(shí)間超過(guò)了這個(gè)值,會(huì)被回收掉。如果任務(wù)很多,并且每個(gè)任務(wù)的執(zhí)行時(shí)間比較短,避免線程重復(fù)創(chuàng)建和回收,可以調(diào)大這個(gè)時(shí)間,提高線程的利用率。

          unit:keepAliveTIme的時(shí)間單位,可以選擇的單位有天、小時(shí)、分鐘、毫秒、微妙、千分之一毫秒和納秒。類型是一個(gè)枚舉java.util.concurrent.TimeUnit,這個(gè)枚舉也經(jīng)常使用,有興趣的可以看一下其源碼

          workQueue:工作隊(duì)列,用于緩存待處理任務(wù)的阻塞隊(duì)列,常見的有4種,后面有介紹

          threadFactory:線程池中創(chuàng)建線程的工廠,可以通過(guò)線程工廠給每個(gè)創(chuàng)建出來(lái)的線程設(shè)置更有意義的名字

          handler:飽和策略,當(dāng)線程池?zé)o法處理新來(lái)的任務(wù)了,那么需要提供一種策略處理提交的新任務(wù),默認(rèn)有4種策略,文章后面會(huì)提到

          線程池的簡(jiǎn)單使用示例代碼:

          public class Demo1 {
              static ThreadPoolExecutor executor = new ThreadPoolExecutor(3,
                      5,
                      10,
                      TimeUnit.SECONDS,
                      new ArrayBlockingQueue<Runnable>(10),
                      Executors.defaultThreadFactory(),
                      new ThreadPoolExecutor.AbortPolicy());

              public static void main(String[] args{
                  for (int i = 0; i < 10; i++) {
                      int j = i;
                      String taskName = "任務(wù)" + j;
                      executor.execute(() -> {
                          //模擬任務(wù)內(nèi)部處理耗時(shí)
                          try {
                              TimeUnit.SECONDS.sleep(j);
                          } catch (InterruptedException e) {
                              e.printStackTrace();
                          }
                          System.out.println(Thread.currentThread().getName() + taskName + "處理完畢");
                      });
                  }
                  //關(guān)閉線程池
                  executor.shutdown();
              }
          }

          任務(wù)調(diào)度流程

          • 首先檢測(cè)線程池運(yùn)行狀態(tài),如果不是RUNNING,則直接拒絕,線程池要保證在RUNNING的狀態(tài)下執(zhí)行任務(wù)。

          • 如果workerCount < corePoolSize,則創(chuàng)建并啟動(dòng)一個(gè)線程來(lái)執(zhí)行新提交的任務(wù)。

          • 如果workerCount >= corePoolSize,且線程池內(nèi)的阻塞隊(duì)列未滿,則將任務(wù)添加到該阻塞隊(duì)列中。

          • 如果workerCount >= corePoolSize && workerCount < maximumPoolSize,且線程池內(nèi)的阻塞隊(duì)列已滿,則創(chuàng)建并啟動(dòng)一個(gè)線程來(lái)執(zhí)行新提交的任務(wù)。

          • 如果workerCount >= maximumPoolSize,并且線程池內(nèi)的阻塞隊(duì)列已滿, 則根據(jù)拒絕策略來(lái)處理該任務(wù), 默認(rèn)的處理方式是直接拋異常。

          線程池中常見5種工作隊(duì)列

          任務(wù)太多的時(shí)候,工作隊(duì)列用于暫時(shí)緩存待處理的任務(wù),JDK中常見的5種阻塞隊(duì)列:

          • ArrayBlockingQueue:是一個(gè)基于數(shù)組結(jié)構(gòu)的有界阻塞隊(duì)列,此隊(duì)列按照先進(jìn)先出原則對(duì)元素進(jìn)行排序

          • LinkedBlockingQueue:是一個(gè)基于鏈表結(jié)構(gòu)的阻塞隊(duì)列,此隊(duì)列按照先進(jìn)先出排序元素,吞吐量通常要高于ArrayBlockingQueue。靜態(tài)工廠方法Executors.newFixedThreadPool使用了這個(gè)隊(duì)列。

          • SynchronousQueue :一個(gè)不存儲(chǔ)元素的阻塞隊(duì)列,每個(gè)插入操作必須等到另外一個(gè)線程調(diào)用移除操作,否則插入操作一直處理阻塞狀態(tài),吞吐量通常要高于LinkedBlockingQueue,靜態(tài)工廠方法Executors.newCachedThreadPool使用這個(gè)隊(duì)列

          • PriorityBlockingQueue:優(yōu)先級(jí)隊(duì)列,進(jìn)入隊(duì)列的元素按照優(yōu)先級(jí)會(huì)進(jìn)行排序

          四種常見飽和策略

          • AbortPolicy:直接拋出異常

          • CallerRunsPolicy:在當(dāng)前調(diào)用者的線程中運(yùn)行任務(wù),即隨丟來(lái)的任務(wù),由他自己去處理

          • DiscardOldestPolicy:丟棄隊(duì)列中最老的一個(gè)任務(wù),即丟棄隊(duì)列頭部的一個(gè)任務(wù),然后執(zhí)行當(dāng)前傳入的任務(wù)

          • DiscardPolicy:不處理,直接丟棄掉,方法內(nèi)部為空

          Executors類

          Executors類,提供了一系列工廠方法用于創(chuàng)建線程池,返回的線程池都實(shí)現(xiàn)了ExecutorService接口。常用的方法有:

          • newSingleThreadExecutor

          public static ExecutorService newSingleThreadExecutor()
          public static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory)

          創(chuàng)建一個(gè)單線程的線程池。這個(gè)線程池只有一個(gè)線程在工作,也就是相當(dāng)于單線程串行執(zhí)行所有任務(wù)。如果這個(gè)唯一的線程因?yàn)楫惓=Y(jié)束,那么會(huì)有一個(gè)新的線程來(lái)替代它。此線程池保證所有任務(wù)的執(zhí)行順序按照任務(wù)的提交順序執(zhí)行。內(nèi)部使用了無(wú)限容量的LinkedBlockingQueue阻塞隊(duì)列來(lái)緩存任務(wù),任務(wù)如果比較多,單線程如果處理不過(guò)來(lái),會(huì)導(dǎo)致隊(duì)列堆滿,引發(fā)OOM。

          • newFixedThreadPool

          public static ExecutorService newFixedThreadPool(int nThreads)
          public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory)

          創(chuàng)建固定大小的線程池。每次提交一個(gè)任務(wù)就創(chuàng)建一個(gè)線程,直到線程達(dá)到線程池的最大大小。線程池的大小一旦達(dá)到最大值就會(huì)保持不變,在提交新任務(wù),任務(wù)將會(huì)進(jìn)入等待隊(duì)列中等待。如果某個(gè)線程因?yàn)閳?zhí)行異常而結(jié)束,那么線程池會(huì)補(bǔ)充一個(gè)新線程。內(nèi)部使用了無(wú)限容量的LinkedBlockingQueue阻塞隊(duì)列來(lái)緩存任務(wù),任務(wù)如果比較多,如果處理不過(guò)來(lái),會(huì)導(dǎo)致隊(duì)列堆滿,引發(fā)OOM。

          • newCachedThreadPool

          public static ExecutorService newCachedThreadPool()
          public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory)

          創(chuàng)建一個(gè)可緩存的線程池。如果線程池的大小超過(guò)了處理任務(wù)所需要的線程,那么就會(huì)回收部分空閑(60秒處于等待任務(wù)到來(lái))的線程,當(dāng)任務(wù)數(shù)增加時(shí),此線程池又可以智能的添加新線程來(lái)處理任務(wù)。此線程池的最大值是Integer的最大值(2^31-1)。內(nèi)部使用了SynchronousQueue同步隊(duì)列來(lái)緩存任務(wù),此隊(duì)列的特性是放入任務(wù)時(shí)必須要有對(duì)應(yīng)的線程獲取任務(wù),任務(wù)才可以放入成功。如果處理的任務(wù)比較耗時(shí),任務(wù)來(lái)的速度也比較快,會(huì)創(chuàng)建太多的線程引發(fā)OOM。

          • newScheduledThreadPool

          public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize)
          public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize, ThreadFactory threadFactory)

          創(chuàng)建一個(gè)大小無(wú)限的線程池。此線程池支持定時(shí)以及周期性執(zhí)行任務(wù)的需求。

          在《阿里巴巴java開發(fā)手冊(cè)》中指出了線程資源必須通過(guò)線程池提供,不允許在應(yīng)用中自行顯示的創(chuàng)建線程,這樣一方面是線程的創(chuàng)建更加規(guī)范,可以合理控制開辟線程的數(shù)量;另一方面線程的細(xì)節(jié)管理交給線程池處理,優(yōu)化了資源的開銷。而線程池不允許使用Executors去創(chuàng)建,而要通過(guò)ThreadPoolExecutor方式,這一方面是由于jdk中Executor框架雖然提供了如newFixedThreadPool()、newSingleThreadExecutor()、newCachedThreadPool()等創(chuàng)建線程池的方法,但都有其局限性,不夠靈活;另外由于前面幾種方法內(nèi)部也是通過(guò)ThreadPoolExecutor方式實(shí)現(xiàn),使用ThreadPoolExecutor有助于大家明確線程池的運(yùn)行規(guī)則,創(chuàng)建符合自己的業(yè)務(wù)場(chǎng)景需要的線程池,避免資源耗盡的風(fēng)險(xiǎn)。

          線程池雖然在并發(fā)編程里很強(qiáng)大,但線程池使用面臨的核心的問(wèn)題在于:線程池的參數(shù)并不好配置。一方面線程池的運(yùn)行機(jī)制不是很好理解,配置合理需要強(qiáng)依賴開發(fā)人員的個(gè)人經(jīng)驗(yàn)和知識(shí);另一方面,線程池執(zhí)行的情況和任務(wù)類型相關(guān)性較大,IO密集型和CPU密集型的任務(wù)運(yùn)行起來(lái)的情況差異非常大,這導(dǎo)致業(yè)界并沒(méi)有一些成熟的經(jīng)驗(yàn)策略幫助開發(fā)人員參考。

          美團(tuán)方案

          比如網(wǎng)上流傳的比較多的一個(gè)策略:

          • 如果是CPU密集型任務(wù),就需要盡量壓榨CPU,參考值可以設(shè)為 N(CPU)+1(比如是4核心 就配置為5)

          • 如果是IO密集型任務(wù),參考值可以設(shè)置為2*N(CPU)

          CPU密集型的為什么要+1呢?《Java并發(fā)編程實(shí)戰(zhàn)》給出的原因是:即使當(dāng)計(jì)算(CPU)密集型的線程偶爾由于頁(yè)缺失故障或者其他原因而暫停時(shí),這個(gè)“額外”的線程也能確保 CPU 的時(shí)鐘周期不會(huì)被浪費(fèi)。

          這里先來(lái)看看美團(tuán)幫我們總結(jié)的現(xiàn)在業(yè)界的一些線程池調(diào)參方案:

          第一套方案是并發(fā)編程實(shí)戰(zhàn)給出的,明顯太理論化了,和實(shí)際業(yè)務(wù)想去甚遠(yuǎn)!

          N(threads) = N(Cpu個(gè)數(shù))*U(cpu的使用率)*(1+ 等待時(shí)間/計(jì)算時(shí)間)

          第二套方案就沒(méi)有考慮多個(gè)業(yè)務(wù)線程池的情況。
          第三套方案的用到了TPS來(lái)參與計(jì)算,但是這也是流量恒定情況下算出來(lái)的,真實(shí)情況往往比較隨機(jī)。

          有啥比較好的辦法嗎?——那就是:線程池參數(shù)動(dòng)態(tài)化,采用這種方案最好就是用這么一個(gè)辦法來(lái)做:

          • 簡(jiǎn)化線程池配置:線程池構(gòu)造參數(shù)有8個(gè),但是最核心的是3個(gè):corePoolSize、maximumPoolSize,workQueue,它們最大程度地決定了線程池的任務(wù)分配和線程分配策略

          • 參數(shù)可動(dòng)態(tài)修改:為了解決參數(shù)不好配,修改參數(shù)成本高等問(wèn)題

          • 加線程池監(jiān)控

          為什么能做到動(dòng)態(tài)修改線程池參數(shù)呢?這是因?yàn)镴DK本身就提供api方法支持動(dòng)態(tài)的修改:

          至于如何在運(yùn)行時(shí)狀態(tài)實(shí)時(shí)查看,這里也有一個(gè)辦法:用戶基于JDK原生線程池ThreadPoolExecutor提供的幾個(gè)public的getter方法,可以讀取到當(dāng)前線程池的運(yùn)行狀態(tài)以及參數(shù):

          用戶基于這個(gè)功能可以了解線程池的實(shí)時(shí)狀態(tài),比如當(dāng)前有多少個(gè)工作線程,執(zhí)行了多少個(gè)任務(wù),隊(duì)列中等待的任務(wù)數(shù)等等。

          Netty進(jìn)階指南給出來(lái)的方案

          在Netty服務(wù)編寫的過(guò)程中,也要涉及到兩個(gè)線程池的參數(shù)配置,尤其是IO線程池的配置,這里書中也給了一套經(jīng)驗(yàn)方案來(lái)針對(duì)線程的監(jiān)控情況,可以參考:
          同樣的先用CPU核數(shù)*2,看看是否存在瓶頸,運(yùn)行時(shí)的監(jiān)控則用比較土的辦法了:

          • 打印thread dump,同時(shí)獲取當(dāng)時(shí)cpu排在前面幾個(gè)的線程號(hào)

          • 然后在線程dump文件中去對(duì)應(yīng)的線程號(hào)堆棧

          • 然后在堆棧中查找是否有SelectotImpl.lookAndDoSelect處的lock信息

          如果多次采集都發(fā)現(xiàn)有這堆信息的話,說(shuō)明此時(shí)此刻的IO線程比較空閑,無(wú)需調(diào)整;但是如果一直在read或者write的執(zhí)行處,則說(shuō)明IO較為繁忙,可以適當(dāng)?shù)娜フ{(diào)大NioEventLoop線程的個(gè)數(shù)來(lái)提升網(wǎng)絡(luò)的讀寫性能。但是這邊線程數(shù)的改動(dòng)就不是動(dòng)態(tài)化的了,服務(wù)啟動(dòng)后指定的線程數(shù)就不能再修改了。

          看完本文有收獲?請(qǐng)轉(zhuǎn)發(fā)分享給更多人

          往期資源:


          Flutter 移動(dòng)應(yīng)用開發(fā)實(shí)戰(zhàn) 視頻(開發(fā)你自己的抖音APP)
          Java面試進(jìn)階訓(xùn)練營(yíng) 第2季(分布式篇)
          Java高級(jí) - 分布式系統(tǒng)開發(fā)技術(shù)視頻


          瀏覽 40
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <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>
                  亚洲成人内射 | 欧美A片久久 | www-91AV | 亚洲第九页 | 国产一区二区三区黄片 |