<p id="m2nkj"><option id="m2nkj"><big id="m2nkj"></big></option></p>
    <strong id="m2nkj"></strong>
    <ruby id="m2nkj"></ruby>

    <var id="m2nkj"></var>
  • 線程池的7種創(chuàng)建方式,強烈推薦你用它...

    共 10727字,需瀏覽 22分鐘

     ·

    2020-12-20 12:13

    作者 | 王磊

    來源 | Java中文社群(ID:javacn666)

    轉(zhuǎn)載請聯(lián)系授權(quán)(微信ID:GG_Stone)

    根據(jù)摩爾定律所說:集成電路上可容納的晶體管數(shù)量每 18 個月翻一番,因此 CPU 上的晶體管數(shù)量會越來越多。

    但隨著時間的推移,集成電路上可容納的晶體管數(shù)量已趨向飽和,摩爾定律也漸漸失效,因此多核 CPU 逐漸變?yōu)橹髁?,與之相對應的多線程編程也開始變得普及和流行起來,這當然也是很久之前的事了,對于現(xiàn)在而言多線程編程已經(jīng)成為程序員必備的職業(yè)技能了,那接下來我們就來盤一盤“線程池”這個多線程編程中最重要的話題。

    什么是線程池?

    線程池(ThreadPool)是一種基于池化思想管理和使用線程的機制。它是將多個線程預先存儲在一個“池子”內(nèi),當有任務出現(xiàn)時可以避免重新創(chuàng)建和銷毀線程所帶來性能開銷,只需要從“池子”內(nèi)取出相應的線程執(zhí)行對應的任務即可。

    池化思想在計算機的應用也比較廣泛,比如以下這些:

    • 內(nèi)存池(Memory Pooling):預先申請內(nèi)存,提升申請內(nèi)存速度,減少內(nèi)存碎片。
    • 連接池(Connection Pooling):預先申請數(shù)據(jù)庫連接,提升申請連接的速度,降低系統(tǒng)的開銷。
    • 實例池(Object Pooling):循環(huán)使用對象,減少資源在初始化和釋放時的昂貴損耗。

    線程池的優(yōu)勢主要體現(xiàn)在以下 4 點:

    1. 降低資源消耗:通過池化技術(shù)重復利用已創(chuàng)建的線程,降低線程創(chuàng)建和銷毀造成的損耗。
    2. 提高響應速度:任務到達時,無需等待線程創(chuàng)建即可立即執(zhí)行。
    3. 提高線程的可管理性:線程是稀缺資源,如果無限制創(chuàng)建,不僅會消耗系統(tǒng)資源,還會因為線程的不合理分布導致資源調(diào)度失衡,降低系統(tǒng)的穩(wěn)定性。使用線程池可以進行統(tǒng)一的分配、調(diào)優(yōu)和監(jiān)控。
    4. 提供更多更強大的功能:線程池具備可拓展性,允許開發(fā)人員向其中增加更多的功能。比如延時定時線程池ScheduledThreadPoolExecutor,就允許任務延期執(zhí)行或定期執(zhí)行。

    同時阿里巴巴在其《Java開發(fā)手冊》中也強制規(guī)定:線程資源必須通過線程池提供,不允許在應用中自行顯式創(chuàng)建線程

    說明:線程池的好處是減少在創(chuàng)建和銷毀線程上所消耗的時間以及系統(tǒng)資源的開銷,解決資源不足的問題。如果不使用線程池,有可能造成系統(tǒng)創(chuàng)建大量同類線程而導致消耗完內(nèi)存或者“過度切換”的問題。

    知道了什么是線程池以及為什要用線程池之后,我們再來看怎么用線程池。

    線程池使用

    線程池的創(chuàng)建方法總共有 7 種,但總體來說可分為 2 類:

    • 一類是通過 ThreadPoolExecutor 創(chuàng)建的線程池;
    • 另一個類是通過 Executors 創(chuàng)建的線程池。


    線程池的創(chuàng)建方式總共包含以下 7 種(其中 6 種是通過 Executors 創(chuàng)建的,1 種是通過 ThreadPoolExecutor 創(chuàng)建的):

    1. Executors.newFixedThreadPool:創(chuàng)建一個固定大小的線程池,可控制并發(fā)的線程數(shù),超出的線程會在隊列中等待;
    2. Executors.newCachedThreadPool:創(chuàng)建一個可緩存的線程池,若線程數(shù)超過處理所需,緩存一段時間后會回收,若線程數(shù)不夠,則新建線程;
    3. Executors.newSingleThreadExecutor:創(chuàng)建單個線程數(shù)的線程池,它可以保證先進先出的執(zhí)行順序;
    4. Executors.newScheduledThreadPool:創(chuàng)建一個可以執(zhí)行延遲任務的線程池;
    5. Executors.newSingleThreadScheduledExecutor:創(chuàng)建一個單線程的可以執(zhí)行延遲任務的線程池;
    6. Executors.newWorkStealingPool:創(chuàng)建一個搶占式執(zhí)行的線程池(任務執(zhí)行順序不確定)【JDK 1.8 添加】。
    7. ThreadPoolExecutor:最原始的創(chuàng)建線程池的方式,它包含了 7 個參數(shù)可供設置,后面會詳細講。

    單線程池的意義從以上代碼可以看出 newSingleThreadExecutor 和 newSingleThreadScheduledExecutor 創(chuàng)建的都是單線程池,那么單線程池的意義是什么呢?答:雖然是單線程池,但提供了工作隊列,生命周期管理,工作線程維護等功能。

    那接下來我們來看每種線程池創(chuàng)建的具體使用。

    1.FixedThreadPool

    創(chuàng)建一個固定大小的線程池,可控制并發(fā)的線程數(shù),超出的線程會在隊列中等待。

    使用示例如下:

    public?static?void?fixedThreadPool()?{
    ????//?創(chuàng)建?2?個數(shù)據(jù)級的線程池
    ????ExecutorService?threadPool?=?Executors.newFixedThreadPool(2);

    ????//?創(chuàng)建任務
    ????Runnable?runnable?=?new?Runnable()?{
    ????????@Override
    ????????public?void?run()?{
    ????????????System.out.println("任務被執(zhí)行,線程:"?+?Thread.currentThread().getName());
    ????????}
    ????};

    ????//?線程池執(zhí)行任務(一次添加?4?個任務)
    ????//?執(zhí)行任務的方法有兩種:submit?和?execute
    ????threadPool.submit(runnable);??//?執(zhí)行方式?1:submit
    ????threadPool.execute(runnable);?//?執(zhí)行方式?2:execute
    ????threadPool.execute(runnable);
    ????threadPool.execute(runnable);
    }

    執(zhí)行結(jié)果如下:

    如果覺得以上方法比較繁瑣,還用更簡單的使用方法,如下代碼所示:

    public?static?void?fixedThreadPool()?{
    ????//?創(chuàng)建線程池
    ????ExecutorService?threadPool?=?Executors.newFixedThreadPool(2);
    ????//?執(zhí)行任務
    ????threadPool.execute(()?->?{
    ????????System.out.println("任務被執(zhí)行,線程:"?+?Thread.currentThread().getName());
    ????});
    }

    2.CachedThreadPool

    創(chuàng)建一個可緩存的線程池,若線程數(shù)超過處理所需,緩存一段時間后會回收,若線程數(shù)不夠,則新建線程。

    使用示例如下:

    public?static?void?cachedThreadPool()?{
    ????//?創(chuàng)建線程池
    ????ExecutorService?threadPool?=?Executors.newCachedThreadPool();
    ????//?執(zhí)行任務
    ????for?(int?i?=?0;?i?10;?i++)?{
    ????????threadPool.execute(()?->?{
    ????????????System.out.println("任務被執(zhí)行,線程:"?+?Thread.currentThread().getName());
    ????????????try?{
    ????????????????TimeUnit.SECONDS.sleep(1);
    ????????????}?catch?(InterruptedException?e)?{
    ????????????}
    ????????});
    ????}
    }

    執(zhí)行結(jié)果如下:從上述結(jié)果可以看出,線程池創(chuàng)建了 10 個線程來執(zhí)行相應的任務。

    3.SingleThreadExecutor

    創(chuàng)建單個線程數(shù)的線程池,它可以保證先進先出的執(zhí)行順序。

    使用示例如下:

    public?static?void?singleThreadExecutor()?{
    ????//?創(chuàng)建線程池
    ????ExecutorService?threadPool?=?Executors.newSingleThreadExecutor();
    ????//?執(zhí)行任務
    ????for?(int?i?=?0;?i?10;?i++)?{
    ????????final?int?index?=?i;
    ????????threadPool.execute(()?->?{
    ????????????System.out.println(index?+?":任務被執(zhí)行");
    ????????????try?{
    ????????????????TimeUnit.SECONDS.sleep(1);
    ????????????}?catch?(InterruptedException?e)?{
    ????????????}
    ????????});
    ????}
    }

    執(zhí)行結(jié)果如下:

    4.ScheduledThreadPool

    創(chuàng)建一個可以執(zhí)行延遲任務的線程池。

    使用示例如下:

    public?static?void?scheduledThreadPool()?{
    ????//?創(chuàng)建線程池
    ????ScheduledExecutorService?threadPool?=?Executors.newScheduledThreadPool(5);
    ????//?添加定時執(zhí)行任務(1s?后執(zhí)行)
    ????System.out.println("添加任務,時間:"?+?new?Date());
    ????threadPool.schedule(()?->?{
    ????????System.out.println("任務被執(zhí)行,時間:"?+?new?Date());
    ????????try?{
    ????????????TimeUnit.SECONDS.sleep(1);
    ????????}?catch?(InterruptedException?e)?{
    ????????}
    ????},?1,?TimeUnit.SECONDS);
    }

    執(zhí)行結(jié)果如下:從上述結(jié)果可以看出,任務在 1 秒之后被執(zhí)行了,符合我們的預期。

    5.SingleThreadScheduledExecutor

    創(chuàng)建一個單線程的可以執(zhí)行延遲任務的線程池。

    使用示例如下:

    public?static?void?SingleThreadScheduledExecutor()?{
    ????//?創(chuàng)建線程池
    ????ScheduledExecutorService?threadPool?=?Executors.newSingleThreadScheduledExecutor();
    ????//?添加定時執(zhí)行任務(2s?后執(zhí)行)
    ????System.out.println("添加任務,時間:"?+?new?Date());
    ????threadPool.schedule(()?->?{
    ????????System.out.println("任務被執(zhí)行,時間:"?+?new?Date());
    ????????try?{
    ????????????TimeUnit.SECONDS.sleep(1);
    ????????}?catch?(InterruptedException?e)?{
    ????????}
    ????},?2,?TimeUnit.SECONDS);
    }

    執(zhí)行結(jié)果如下:從上述結(jié)果可以看出,任務在 2 秒之后被執(zhí)行了,符合我們的預期。

    6.newWorkStealingPool

    創(chuàng)建一個搶占式執(zhí)行的線程池(任務執(zhí)行順序不確定),注意此方法只有在 JDK 1.8+ 版本中才能使用。

    使用示例如下:

    public?static?void?workStealingPool()?{
    ????//?創(chuàng)建線程池
    ????ExecutorService?threadPool?=?Executors.newWorkStealingPool();
    ????//?執(zhí)行任務
    ????for?(int?i?=?0;?i?10;?i++)?{
    ????????final?int?index?=?i;
    ????????threadPool.execute(()?->?{
    ????????????System.out.println(index?+?"?被執(zhí)行,線程名:"?+?Thread.currentThread().getName());
    ????????});
    ????}
    ????//?確保任務執(zhí)行完成
    ????while?(!threadPool.isTerminated())?{
    ????}
    }

    執(zhí)行結(jié)果如下:從上述結(jié)果可以看出,任務的執(zhí)行順序是不確定的,因為它是搶占式執(zhí)行的。

    7.ThreadPoolExecutor

    最原始的創(chuàng)建線程池的方式,它包含了 7 個參數(shù)可供設置。

    使用示例如下:

    public?static?void?myThreadPoolExecutor()?{
    ????//?創(chuàng)建線程池
    ????ThreadPoolExecutor?threadPool?=?new?ThreadPoolExecutor(5,?10,?100,?TimeUnit.SECONDS,?new?LinkedBlockingQueue<>(10));
    ????//?執(zhí)行任務
    ????for?(int?i?=?0;?i?10;?i++)?{
    ????????final?int?index?=?i;
    ????????threadPool.execute(()?->?{
    ????????????System.out.println(index?+?"?被執(zhí)行,線程名:"?+?Thread.currentThread().getName());
    ????????????try?{
    ????????????????Thread.sleep(1000);
    ????????????}?catch?(InterruptedException?e)?{
    ????????????????e.printStackTrace();
    ????????????}
    ????????});
    ????}
    }

    執(zhí)行結(jié)果如下:

    ThreadPoolExecutor 參數(shù)介紹

    ThreadPoolExecutor 最多可以設置 7 個參數(shù),如下代碼所示:

    ?public?ThreadPoolExecutor(int?corePoolSize,
    ???????????????????????????int?maximumPoolSize,
    ???????????????????????????long?keepAliveTime,
    ???????????????????????????TimeUnit?unit,
    ???????????????????????????BlockingQueue?workQueue,
    ???????????????????????????ThreadFactory?threadFactory,
    ???????????????????????????RejectedExecutionHandler?handler)
    ?
    {
    ?????//?省略...
    ?}

    7 個參數(shù)代表的含義如下:

    參數(shù) 1:corePoolSize

    核心線程數(shù),線程池中始終存活的線程數(shù)。

    參數(shù) 2:maximumPoolSize

    最大線程數(shù),線程池中允許的最大線程數(shù),當線程池的任務隊列滿了之后可以創(chuàng)建的最大線程數(shù)。

    參數(shù) 3:keepAliveTime

    最大線程數(shù)可以存活的時間,當線程中沒有任務執(zhí)行時,最大線程就會銷毀一部分,最終保持核心線程數(shù)量的線程。

    參數(shù) 4:unit:

    單位是和參數(shù) 3 存活時間配合使用的,合在一起用于設定線程的存活時間 ,參數(shù) keepAliveTime 的時間單位有以下 7 種可選:

    • TimeUnit.DAYS:天
    • TimeUnit.HOURS:小時
    • TimeUnit.MINUTES:分
    • TimeUnit.SECONDS:秒
    • TimeUnit.MILLISECONDS:毫秒
    • TimeUnit.MICROSECONDS:微妙
    • TimeUnit.NANOSECONDS:納秒

    參數(shù) 5:workQueue

    一個阻塞隊列,用來存儲線程池等待執(zhí)行的任務,均為線程安全,它包含以下 7 種類型:

    • ArrayBlockingQueue:一個由數(shù)組結(jié)構(gòu)組成的有界阻塞隊列。
    • LinkedBlockingQueue:一個由鏈表結(jié)構(gòu)組成的有界阻塞隊列。
    • SynchronousQueue:一個不存儲元素的阻塞隊列,即直接提交給線程不保持它們。
    • PriorityBlockingQueue:一個支持優(yōu)先級排序的無界阻塞隊列。
    • DelayQueue:一個使用優(yōu)先級隊列實現(xiàn)的無界阻塞隊列,只有在延遲期滿時才能從中提取元素。
    • LinkedTransferQueue:一個由鏈表結(jié)構(gòu)組成的無界阻塞隊列。與SynchronousQueue類似,還含有非阻塞方法。
    • LinkedBlockingDeque:一個由鏈表結(jié)構(gòu)組成的雙向阻塞隊列。

    較常用的是 LinkedBlockingQueueSynchronous,線程池的排隊策略與 BlockingQueue 有關(guān)。

    參數(shù) 6:threadFactory

    線程工廠,主要用來創(chuàng)建線程,默認為正常優(yōu)先級、非守護線程。

    參數(shù) 7:handler

    拒絕策略,拒絕處理任務時的策略,系統(tǒng)提供了 4 種可選:

    • AbortPolicy:拒絕并拋出異常。
    • CallerRunsPolicy:使用當前調(diào)用的線程來執(zhí)行此任務。
    • DiscardOldestPolicy:拋棄隊列頭部(最舊)的一個任務,并執(zhí)行當前任務。
    • DiscardPolicy:忽略并拋棄當前任務。

    默認策略為 AbortPolicy。

    線程池的執(zhí)行流程

    ThreadPoolExecutor 關(guān)鍵節(jié)點的執(zhí)行流程如下:

    • 當線程數(shù)小于核心線程數(shù)時,創(chuàng)建線程。
    • 當線程數(shù)大于等于核心線程數(shù),且任務隊列未滿時,將任務放入任務隊列。
    • 當線程數(shù)大于等于核心線程數(shù),且任務隊列已滿:若線程數(shù)小于最大線程數(shù),創(chuàng)建線程;若線程數(shù)等于最大線程數(shù),拋出異常,拒絕任務。

    線程池的執(zhí)行流程如下圖所示:

    線程拒絕策略

    我們來演示一下 ThreadPoolExecutor 的拒絕策略的觸發(fā),我們使用 DiscardPolicy? 的拒絕策略,它會忽略并拋棄當前任務的策略,實現(xiàn)代碼如下:

    public?static?void?main(String[]?args)?{
    ????//?任務的具體方法
    ????Runnable?runnable?=?new?Runnable()?{
    ????????@Override
    ????????public?void?run()?{
    ????????????System.out.println("當前任務被執(zhí)行,執(zhí)行時間:"?+?new?Date()?+
    ???????????????????????????????"?執(zhí)行線程:"?+?Thread.currentThread().getName());
    ????????????try?{
    ????????????????//?等待?1s
    ????????????????TimeUnit.SECONDS.sleep(1);
    ????????????}?catch?(InterruptedException?e)?{
    ????????????????e.printStackTrace();
    ????????????}
    ????????}
    ????};
    ????//?創(chuàng)建線程,線程的任務隊列的長度為?1
    ????ThreadPoolExecutor?threadPool?=?new?ThreadPoolExecutor(1,?1,
    ???????????????????????????????????????????????????????????100,?TimeUnit.SECONDS,?new?LinkedBlockingQueue<>(1),
    ???????????????????????????????????????????????????????????new?ThreadPoolExecutor.DiscardPolicy());
    ????//?添加并執(zhí)行?4?個任務
    ????threadPool.execute(runnable);
    ????threadPool.execute(runnable);
    ????threadPool.execute(runnable);
    ????threadPool.execute(runnable);
    }

    我們創(chuàng)建了一個核心線程數(shù)和最大線程數(shù)都為 1 的線程池,并且給線程池的任務隊列設置為 1,這樣當我們有 2 個以上的任務時就會觸發(fā)拒絕策略,執(zhí)行的結(jié)果如下圖所示:從上述結(jié)果可以看出只有兩個任務被正確執(zhí)行了,其他多余的任務就被舍棄并忽略了。其他拒絕策略的使用類似,這里就不一一贅述了。

    自定義拒絕策略

    除了 Java 自身提供的 4 種拒絕策略之外,我們也可以自定義拒絕策略,示例代碼如下:

    public?static?void?main(String[]?args)?{
    ????//?任務的具體方法
    ????Runnable?runnable?=?new?Runnable()?{
    ????????@Override
    ????????public?void?run()?{
    ????????????System.out.println("當前任務被執(zhí)行,執(zhí)行時間:"?+?new?Date()?+
    ???????????????????????????????"?執(zhí)行線程:"?+?Thread.currentThread().getName());
    ????????????try?{
    ????????????????//?等待?1s
    ????????????????TimeUnit.SECONDS.sleep(1);
    ????????????}?catch?(InterruptedException?e)?{
    ????????????????e.printStackTrace();
    ????????????}
    ????????}
    ????};
    ????//?創(chuàng)建線程,線程的任務隊列的長度為?1
    ????ThreadPoolExecutor?threadPool?=?new?ThreadPoolExecutor(1,?1,
    ???????????????????????????????????????????????????????????100,?TimeUnit.SECONDS,?new?LinkedBlockingQueue<>(1),
    ???????????????????????????????????????????????????????????new?RejectedExecutionHandler()?{
    ???????????????????????????????????????????????????????????????@Override
    ???????????????????????????????????????????????????????????????public?void?rejectedExecution(Runnable?r,?ThreadPoolExecutor?executor)?{
    ???????????????????????????????????????????????????????????????????//?執(zhí)行自定義拒絕策略的相關(guān)操作
    ???????????????????????????????????????????????????????????????????System.out.println("我是自定義拒絕策略~");
    ???????????????????????????????????????????????????????????????}
    ???????????????????????????????????????????????????????????});
    ????//?添加并執(zhí)行?4?個任務
    ????threadPool.execute(runnable);
    ????threadPool.execute(runnable);
    ????threadPool.execute(runnable);
    ????threadPool.execute(runnable);
    }

    程序的執(zhí)行結(jié)果如下:

    究竟選用哪種線程池?

    經(jīng)過以上的學習我們對整個線程池也有了一定的認識了,那究竟該如何選擇線程池呢?

    我們來看下阿里巴巴《Java開發(fā)手冊》給我們的答案:

    【強制】線程池不允許使用 Executors 去創(chuàng)建,而是通過 ThreadPoolExecutor 的方式,這樣的處理方式讓寫的同學更加明確線程池的運行規(guī)則,規(guī)避資源耗盡的風險。

    說明:Executors 返回的線程池對象的弊端如下:

    1) FixedThreadPool 和 SingleThreadPool:允許的請求隊列長度為 Integer.MAX_VALUE,可能會堆積大量的請求,從而導致 OOM。

    2)CachedThreadPool:允許的創(chuàng)建線程數(shù)量為 Integer.MAX_VALUE,可能會創(chuàng)建大量的線程,從而導致 OOM。

    所以綜上情況所述,我們推薦使用 ThreadPoolExecutor?的方式進行線程池的創(chuàng)建,因為這種創(chuàng)建方式更可控,并且更加明確了線程池的運行規(guī)則,可以規(guī)避一些未知的風險。

    總結(jié)

    本文我們介紹了線程池的 7 種創(chuàng)建方式,其中最推薦使用的是 ThreadPoolExecutor 的方式進行線程池的創(chuàng)建,ThreadPoolExecutor 最多可以設置 7 個參數(shù),當然設置 5 個參數(shù)也可以正常使用,ThreadPoolExecutor 當任務過多(處理不過來)時提供了 4 種拒絕策略,當然我們也可以自定義拒絕策略,希望本文的內(nèi)容能幫助到你。原創(chuàng)不易,覺得不錯就點個贊再走吧!

    參考 & 鳴謝

    https://tech.meituan.com/2020/04/02/java-pooling-pratice-in-meituan.html

    https://www.cnblogs.com/pcheng/p/13540619.html


    往期推薦

    求求你,別再用wait和notify了!


    2020年終總結(jié):新的“開始”


    提高生產(chǎn)力,最全 MyBatisPlus 講解!


    關(guān)注我,每天陪你進步一點點!

    瀏覽 45
    點贊
    評論
    收藏
    分享

    手機掃一掃分享

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

    手機掃一掃分享

    分享
    舉報
    <p id="m2nkj"><option id="m2nkj"><big id="m2nkj"></big></option></p>
    <strong id="m2nkj"></strong>
    <ruby id="m2nkj"></ruby>

    <var id="m2nkj"></var>
  • 操逼逼逼逼逼 | 欧美性大战久久久 | 澳门成人无码视频免费播放 | 成人A级视频 | 色欲天天网| 国产乱婬AV片免费 | 色老汉视频| 天天躁日日躁AAAAXXXX | 奇米影视7777狠狠狠狠色 | 国产精品天天干 |