日韩av黄I国产麻豆传媒I国产91av视频在线观看I日韩一区二区三区在线看I美女国产在线I麻豆视频国产在线观看I成人黄色短片

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 >

手把手教你手动创建线程池

發(fā)布時間:2025/3/20 42 豆豆
生活随笔 收集整理的這篇文章主要介紹了 手把手教你手动创建线程池 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

??點擊上方?好好學java?,選擇?星標?公眾號

重磅資訊、干貨,第一時間送達 今日推薦:2020,搞個 Mac 玩玩!個人原創(chuàng)+1博客:點擊前往,查看更多 作者:IamHYN 鏈接:https://segmentfault.com/a/1190000021866282

一、為什么要手動創(chuàng)建線程池?

我們之所以要手動創(chuàng)建線程池,是因為 JDK 自帶的工具類所創(chuàng)建的線程池存在一定的弊端,那究竟存在怎么樣的弊端呢?首先來回顧一下 JDK 中線程池框架的繼承關系:

Java線程池框架繼承結構.png ★

JDK 線程池框架繼承關系圖

我們最常用的線程池實現(xiàn)類是ThreadPoolExecutor(紅框里的那個),首先我們來看一下它最通用的構造方法:

/*** 各參數(shù)含義* corePoolSize : 線程池中常駐的線程數(shù)量。核心線程數(shù),默認情況下核心線程會一直存活,即使處于閑置狀態(tài)也不會* 受存活時間 keepAliveTime 的限制,除非將 allowCoreThreadTimeOut 設置為 true。* maximumPoolSize : 線程池所能容納的最大線程數(shù)。超過這個數(shù)的線程將被阻塞。當任務隊列為沒有設置大小的* LinkedBlockingQueue時,這個值無效。* keepAliveTime : 當線程數(shù)量多于 corePoolSize 時,空閑線程的存活時長,超過這個時間就會被回收* unit : keepAliveTime 的時間單位* workQueue : 存放待處理任務的隊列* threadFactory : 線程工廠* handler : 拒絕策略,拒絕無法接收添加的任務*/ public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory,RejectedExecutionHandler handler) { ... ... }

使用 JDK 自帶的 Executors工具類 (圖中藍色框中的那個,這是獨立于線程池繼承關系圖的工具類,類似于 Collections 和 Arrays) 可以直接創(chuàng)建以下種類的線程池:

  • 線程數(shù)量固定的線程池,此方法返回 ThreadPoolExecutor

  • public static ExecutorService newFixedThreadPool(int nThreads) {... ...}
  • 單線程線程池,此方法返回 ThreadPoolExecutor

  • public static ExecutorService newSingleThreadExecutor() {... ...}
  • 可緩存線程的線程池,此方法返回 ThreadPoolExecutor

  • public static ExecutorService newCachedThreadPool() {... ...}
  • 執(zhí)行定時任務的線程池,此方法返回 ScheduledThreadPoolExecutor

  • public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {... ...}
  • 可以拆分執(zhí)行子任務的線程池,此方法返回 ForkJoinPool

  • public static ExecutorService newWorkStealingPool() {... ...}

    JDK 自帶工具類創(chuàng)建的線程池存在的問題

    直接使用這些線程池雖然很方便,但是存在兩個比較大的問題:

  • 有的線程池可以無限添加任務或線程,容易導致 OOM;

  • 就拿我們最常用FixedThreadPool和 CachedThreadPool來說,前者的詳細創(chuàng)建方法如下:

    public static ExecutorService newFixedThreadPool(int nThreads) {return new ThreadPoolExecutor(nThreads, nThreads,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>());}

    可見其任務隊列用的是LinkedBlockingQueue,且沒有指定容量,相當于無界隊列,這種情況下就可以添加大量的任務,甚至達到Integer.MAX_VALUE的數(shù)量級,如果任務大量堆積,可能會導致 OOM。

    而CachedThreadPool的創(chuàng)建方法如下:

    public static ExecutorService newCachedThreadPool() {return new ThreadPoolExecutor(0, Integer.MAX_VALUE,60L, TimeUnit.SECONDS,new SynchronousQueue<Runnable>());}

    這個雖然使用了有界隊列SynchronousQueue,但是最大線程數(shù)設置成了Integer.MAX_VALUE,這就意味著可以創(chuàng)建大量的線程,也有可能導致 OOM。

  • 還有一個問題就是這些線程池的線程都是使用 JDK 自帶的線程工廠 (ThreadFactory)創(chuàng)建的,線程名稱都是類似pool-1-thread-1的形式,第一個數(shù)字是線程池編號,第二個數(shù)字是線程編號,這樣很不利于系統(tǒng)異常時排查問題。

  • 如果你安裝了“阿里編碼規(guī)約”的插件,在使用Executors創(chuàng)建線程池時會出現(xiàn)以下警告信息:

    Alibaba Java Coding Guidelines.png ★

    阿里編碼規(guī)約的警告信息

    為避免這些問題,我們最好還是手動創(chuàng)建線程池。

    二、 如何手動創(chuàng)建線程池

    2.1 定制線程數(shù)量

    首先要說明一點,定制線程池的線程數(shù)并不是多么高深的學問,也不是說一旦線程數(shù)設定不合理,你的程序就無法運行,而是要盡量避免以下兩種極端條件:

  • 線程數(shù)量過大

  • 這會導致過多的線程競爭稀缺的 CPU 和內存資源。CPU 核心的數(shù)量和計算能力是有限的,在分配不到 CPU 執(zhí)行時間的情況下,線程只能處于空閑狀態(tài)。而在JVM 中,線程本身也是對象,也會占用內存,過多的空閑線程自然會浪費寶貴的內存空間。

  • 線程數(shù)量過小

  • 線程池存在的意義,或者說并發(fā)編程的意義就是為了“壓榨”計算機的運算能力,說白了就是別讓 CPU 閑著。如果線程數(shù)量比 CPU 核心數(shù)量還小的話,那么必定有 CPU 核心將處于空閑狀態(tài),這是極大的浪費。

    所以在實際開發(fā)中我們需要根據(jù)實際的業(yè)務場景合理設定線程池的線程數(shù)量,那又如何分析業(yè)務場景呢?我們的業(yè)務場景大致可以分為以下兩大類:

  • CPU (計算)密集型

  • 這種場景需要大量的 CPU 計算,比如加密、計算 hash 等,最佳線程數(shù)為 (CPU 核心數(shù) + 1)。比如8核 CPU,可以把線程數(shù)設置為 9,這樣就足夠了,因為在 CPU 密集型的場景中,每個線程都會在比較大的負荷下工作,很少出現(xiàn)空閑的情況,正好每個線程對應一個 CPU 核心,然后不停地工作,這樣就實現(xiàn)了最優(yōu)利用率。多出的一個線程起到了備胎的作用,在其他線程意外中斷時頂替上去,確保 CPU 不中斷工作。其實也大可不必這么死板,線程數(shù)量設置為 CPU 核心數(shù)的 1 到 2 倍都是可以接受的。

  • I/O 密集型

  • 比如讀寫數(shù)據(jù)庫,讀寫文件或者網(wǎng)絡讀寫等場景。各種 I/O 設備 (比如磁盤)的速度是遠低于 CPU 執(zhí)行速度的,所以在 I/O 密集型的場景下,線程大部分時間都在等待資源而非 CPU 時間片,這樣的話一個 CPU 核心就可以應付很多線程了,也就可以把線程數(shù)量設置大一點。線程具體數(shù)量的計算方法可以參考 Brain Goetz 的建議:

    假設有以下變量:

    * N<sub>threads</sub> = 線程數(shù)量 * N<sub>cpu</sub> = CPU 核心數(shù) * U<sub>cpu</sub> = 期望的CPU 的使用率 ,因為 CPU 可能還要執(zhí)行其他任務 * W = 線程的平均等待資源時間 * C = 線程平均使用 CPU 的計算時間 * W / C = 線程等待時間與計算時間的比率

    這樣為了讓 CPU 達到期望的使用率,最優(yōu)的線程數(shù)量計算公式如下:

    Nthreads = Ncpu Ucpu ( 1 + W / C )

    CPU 核心數(shù)可以通過以下方法獲取:

    int N_CPUS = Runtime.getRuntime().availableProcessors();</code>

    當然,除了 CPU,線程數(shù)量還會受到很多其他因素的影響,比如內存和數(shù)據(jù)庫連接等,需要具體問題具體分析。

    2.2 使用可自定義線程名稱的線程工廠

    這個就簡單多了,可以借助大名鼎鼎的谷歌開源工具庫 Guava,首先引入如下依賴:

    <!-- https://mvnrepository.com/artifact/com.google.guava/guava --> <dependency><groupId>com.google.guava</groupId><artifactId>guava</artifactId><version>28.2-jre</version> </dependency>

    然后我就可以使用其提供的ThreadFactoryBuilder類來創(chuàng)建線程工廠了,Demo 如下:

    public class ThreadPoolDemo {// 線程數(shù)public static final int THREAD_POOL_SIZE = 16;public static void main(String[] args) throws InterruptedException {// 使用 ThreadFactoryBuilder 創(chuàng)建自定義線程名稱的 ThreadFactoryThreadFactory namedThreadFactory = new ThreadFactoryBuilder().setNameFormat("hyn-demo-pool-%d").build();// 創(chuàng)建線程池,其中任務隊列需要結合實際情況設置合理的容量ThreadPoolExecutor executor = new ThreadPoolExecutor(THREAD_POOL_SIZE,THREAD_POOL_SIZE,0L,TimeUnit.MILLISECONDS,new LinkedBlockingQueue<>(1024),namedThreadFactory,new ThreadPoolExecutor.AbortPolicy());// 新建 1000 個任務,每個任務是打印當前線程名稱for (int i = 0; i < 1000; i++) {executor.execute(() -> System.out.println(Thread.currentThread().getName()));}// 優(yōu)雅關閉線程池executor.shutdown();executor.awaitTermination(1000L, TimeUnit.SECONDS);// 任務執(zhí)行完畢后打印"Done"System.out.println("Done");} }

    控制臺打印結果如下:

    ... ... hyn-demo-pool-2 hyn-demo-pool-6 hyn-demo-pool-13 hyn-demo-pool-12 hyn-demo-pool-15 Done

    可見這樣的線程名稱相比pool-1-thread-1更有辨識度,可以為不同用途的線程池設定不同的名稱,便于系統(tǒng)出故障時排查問題。

    三、總結

    本文為大家介紹了手動創(chuàng)建線程池的詳細方法,不過這些都是理論性的內容,而多線程編程是非常注重實踐的一門學問,在實際生產(chǎn)環(huán)境中要綜合考慮各種因素并不斷嘗試,才能實現(xiàn)最佳實踐。

    總結

    以上是生活随笔為你收集整理的手把手教你手动创建线程池的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內容還不錯,歡迎將生活随笔推薦給好友。