<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ù)量到底要如何配置?

          共 5706字,需瀏覽 12分鐘

           ·

          2020-11-20 21:13

          一、前言

          對于從事后端開發(fā)的同學來說,線程是必須要使用了,因為使用它可以提升系統(tǒng)的性能。但是,創(chuàng)建線程和銷毀線程都是比較耗時的操作,頻繁的創(chuàng)建和銷毀線程會浪費很多CPU的資源。
          此外,如果每個任務都創(chuàng)建一個線程去處理,這樣線程會越來越多。我們知道每個線程默認情況下占1M的內(nèi)存空間,如果線程非常多,內(nèi)存資源將會被耗盡。這時,我們需要線程池去管理線程,不會出現(xiàn)內(nèi)存資源被耗盡的情況,也不會出現(xiàn)頻繁創(chuàng)建和銷毀線程的情況,因為它內(nèi)部是可以復用線程的。

          ?

          二、從實戰(zhàn)開始

          在介紹線程池之前,讓我們先看個例子。
          public class MyCallable implements Callable<String> {
          @Override
          public String call() throws Exception { System.out.println("MyCallable call");return "success"; }
          public static void main(String[] args) {
          ExecutorService threadPool = Executors.newSingleThreadExecutor();try {
          Future future = threadPool.submit(new MyCallable()); System.out.println(future.get()); } catch (Exception e) { System.out.println(e); } finally { threadPool.shutdown(); }
          }
          }
          這個類的功能就是使用Executors類的newSingleThreadExecutor方法創(chuàng)建了的一個單線程池,他里面會執(zhí)行Callable線程任務。

          三、創(chuàng)建線程池的方法

          我們仔細看看Executors類,會發(fā)現(xiàn)它里面給我們封裝了不少創(chuàng)建線程池的靜態(tài)方法,如下圖所示:
          其實,我們總結一下其實只有6種:
          1.newCachedThreadPool可緩沖線程池
          它的核心線程數(shù)是0,最大線程數(shù)是integer的最大值,每隔60秒回收一次空閑線程,使用SynchronousQueue隊列。SynchronousQueue隊列比較特殊,內(nèi)部只包含一個元素,插入元素到隊列的線程被阻塞,直到另一個線程從隊列中獲取了隊列中存儲的元素。同樣,如果線程嘗試獲取元素并且當前不存在任何元素,則該線程將被阻塞,直到線程將元素插入隊列。
          2.newFixedThreadPool固定大小線程池
          它的核心線程數(shù) 和 最大線程數(shù)是一樣,都是nThreads變量的值,該變量由用戶自己決定,所以說是固定大小線程池。此外,它的每隔0毫秒回收一次線程,換句話說就是不回收線程,因為它的核心線程數(shù) 和 最大線程數(shù)是一樣,回收了沒有任何意義。此外,使用了LinkedBlockingQueue隊列,該隊列其實是有界隊列,很多人誤解了,只是它的初始大小比較大是integer的最大值。
          3.newScheduledThreadPool定時任務線程池
          它的核心線程數(shù)是corePoolSize變量,需要用戶自己決定,最大線程數(shù)是integer的最大值,同樣,它的每隔0毫秒回收一次線程,換句話說就是不回收線程。使用了DelayedWorkQueue隊列,該隊列具有延時的功能。
          4.newSingleThreadExecutor單個線程池
          其實,跟上面的newFixedThreadPool是一樣的,稍微有一點區(qū)別是核心線程數(shù) 和 最大線程數(shù) 都是1,這就是為什么說它是單線程池的原因。
          5.newSingleThreadScheduledExecutor單線程定時任務線程池
          該線程池是對上面介紹過的ScheduledThreadPoolExecutor定時任務線程池的簡單封裝,核心線程數(shù)固定是1,其他的功能一模一樣。
          6.newWorkStealingPool竊取線程池
          它是JDK1.8增加的新線程池,跟其他的實現(xiàn)方式都不一樣,它底層是通過ForkJoinPool類來實現(xiàn)的。會創(chuàng)建一個含有足夠多線程的線程池,來維持相應的并行級別,它會通過工作竊取的方式,使得多核的 CPU 不會閑置,總會有活著的線程讓 CPU 去運行。
          講了這么多,具體要怎么用呢?
          其實newFixedThreadPool、newCachedThreadPool、newSingleThreadExecutor 和 newWorkStealingPool方法創(chuàng)建和使用線程池的方法是一樣的。這四個方法創(chuàng)建線程池返回值是ExecutorService,通過它的execute方法執(zhí)行線程。
          public class MyWorker implements Runnable {

          @Overridepublic void run() { System.out.println("MyWorker run"); }
          public static void main(String[] args) { ExecutorService threadPool = Executors.newFixedThreadPool(8);try { threadPool.execute(new MyWorker()); } catch (Exception e) { System.out.println(e); } finally { threadPool.shutdown(); }
          }}
          newScheduledThreadPool 和 newSingleThreadScheduledExecutor 方法創(chuàng)建和使用線程池的方法也是一樣的
          public class MyTask implements Runnable {
          @Overridepublic void run() { System.out.println("MyTask call"); }
          public static void main(String[] args) {
          ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(8);try { scheduledExecutorService.schedule(new MyRunnable(), 60, TimeUnit.SECONDS); } finally { scheduledExecutorService.shutdown(); } }}
          以上兩個方法創(chuàng)建的線程池返回值是ScheduledExecutorService,通過它的schedule提交線程,并且可以配置延遲執(zhí)行的時間。

          ?

          四、自定義線程池

          Executors類有這么多方法可以創(chuàng)建線程池,但是阿里巴巴開發(fā)規(guī)范中卻明確規(guī)定不要使用Executors類創(chuàng)建線程池,這是為什么呢?
          newCachedThreadPool可緩沖線程池,它的最大線程數(shù)是integer的最大值,意味著使用它創(chuàng)建的線程池,可以創(chuàng)建非常多的線程,我們都知道一個線程默認情況下占用內(nèi)存1M,如果創(chuàng)建的線程太多,占用內(nèi)存太大,最后肯定會出現(xiàn)內(nèi)存溢出的問題。
          newFixedThreadPool和newSingleThreadExecutor在這里都稱為固定大小線程池,它的隊列使用的LinkedBlockingQueue,我們都知道這個隊列默認大小是integer的最大值,意味著可以往該隊列中加非常多的任務,每個任務也是要內(nèi)存空間的,如果任務太多,最后肯定也會出現(xiàn)內(nèi)存溢出的問題。
          阿里建議使用ThreadPoolExecutor類創(chuàng)建線程池,其實從剛剛看到的Executors類創(chuàng)建線程池的newFixedThreadPool等方法可以看出,它也是使用ThreadPoolExecutor類創(chuàng)建線程池的。
          從上圖可以看出ThreadPoolExecutor類的構造方法有4個,里面包含了很多參數(shù),讓我們先一起認識一下:
          corePoolSize:核心線程數(shù)maximumPoolSize:最大線程數(shù)keepAliveTime:空閑線程回收時間間隔unit:空閑線程回收時間間隔單位workQueue:提交任務的隊列,當線程數(shù)量超過核心線程數(shù)時,可以將任務提交到任務隊列中。比較常用的有:ArrayBlockingQueue; LinkedBlockingQueue; SynchronousQueue;threadFactory:線程工廠,可以自定義線程的一些屬性,比如:名稱或者守護線程等handler:表示當拒絕處理任務時的策略
          ThreadPoolExecutor.AbortPolicy:丟棄任務并拋出RejectedExecutionException異常。ThreadPoolExecutor.DiscardPolicy:也是丟棄任務,但是不拋出異常。ThreadPoolExecutor.DiscardOldestPolicy:丟棄隊列最前面的任務,然后重新嘗試執(zhí)行任務(重復此過程)ThreadPoolExecutor.CallerRunsPolicy:由調用線程處理該任務
          我們根據(jù)上面的內(nèi)容自定義一個線程池:
          public class MyThreadPool implements  Runnable {
          private static final ExecutorService executorService = new ThreadPoolExecutor(8,10,30, TimeUnit.SECONDS,new ArrayBlockingQueue<>(500),new ThreadPoolExecutor.AbortPolicy());
          @Overridepublic void run() { System.out.println("MyThreadPool run"); }
          public static void main(String[] args) {int availableProcessors = Runtime.getRuntime().availableProcessors();try { executorService.execute(new MyThreadPool()); } catch (Exception e) { System.out.println(e); } finally { executorService.shutdown(); } }}
          從上面可以看到,我們使用ThreadPoolExecutor類自定義了一個線程池,它的核心線程數(shù)是8,最大線程數(shù)是 10,空閑線程回收時間是30,單位是秒,存放任務的隊列用的ArrayBlockingQueue,而隊列滿的處理策略用的AbortPolicy。使用這個隊列,基本可以保持線程在系統(tǒng)的可控范圍之內(nèi),不會出現(xiàn)內(nèi)存溢出的問題。但是也不是絕對的,只是出現(xiàn)內(nèi)存溢出的概率比較小。
          當然,阿里巴巴開發(fā)規(guī)范建議不使用Executors類創(chuàng)建線程池,并不表示它完全沒用,在一些低并發(fā)的業(yè)務場景照樣可以使用。

          五、最佳線程數(shù)

          在使用線程池時,很多同學都有這樣的疑問,不知道如何配置線程數(shù)量,今天我們一起探討一下這個問題。
          1.經(jīng)驗值
          配置線程數(shù)量之前,首先要看任務的類型是 IO密集型,還是CPU密集型?
          什么是IO密集型?
          比如:頻繁讀取磁盤上的數(shù)據(jù),或者需要通過網(wǎng)絡遠程調用接口。
          什么是CPU密集型?
          比如:非常復雜的調用,循環(huán)次數(shù)很多,或者遞歸調用層次很深等。
          IO密集型配置線程數(shù)經(jīng)驗值是:2N,其中N代表CPU核數(shù)。
          CPU密集型配置線程數(shù)經(jīng)驗值是:N + 1,其中N代表CPU核數(shù)。
          如果獲取N的值?
          int availableProcessors = Runtime.getRuntime().availableProcessors();
          那么問題來了,混合型(既包含IO密集型,又包含CPU密集型)的如何配置線程數(shù)?
          混合型如果IO密集型,和CPU密集型的執(zhí)行時間相差不太大,可以拆分開,以便于更好配置。如果執(zhí)行時間相差太大,優(yōu)化的意義不大,比如IO密集型耗時60s,CPU密集型耗時1s。
          2.最佳線程數(shù)目算法
          除了上面介紹是經(jīng)驗值之外,其實還提供了計算公式:
          最佳線程數(shù)目 = ((線程等待時間+線程CPU時間)/線程CPU時間 )* CPU數(shù)目
          很顯然線程等待時間所占比例越高,需要越多線程。線程CPU時間所占比例越高,需要越少線程。
          雖說最佳線程數(shù)目算法更準確,但是線程等待時間和線程CPU時間不好測量,實際情況使用得比較少,一般用經(jīng)驗值就差不多了。再配合系統(tǒng)壓測,基本可以確定最適合的線程數(shù)。

          有道無術,術可成;有術無道,止于術

          歡迎大家關注Java之道公眾號


          好文章,我在看??

          瀏覽 55
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

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

          手機掃一掃分享

          分享
          舉報
          <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>
                  精品人妻无码一区二区三区四川人 | 高潮喷水不止视频 | 精品五月丁香婷婷一区 | 三级天天干 | 艹屄视频在线观看 |