成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

多線程到底該設置多少個線程?

開發 后端
今天我們就來看看究竟有哪些計算方法可以復用,線程池中各個參數之間又存在怎樣的關系呢? 本文咱們來慢慢聊。

 我們在使用線程池的時候,會有兩個疑問點:

  •  線程池的線程數量設置過多會導致線程競爭激烈
  •  如果線程數量設置過少的話,還會導致系統無法充分利用計算機資源

那么如何設置才不會影響系統性能呢?

其實線程池的設置是有方法的,不是憑借簡單的估算來決定的。今天我們就來看看究竟有哪些計算方法可以復用,線程池中各個參數之間又存在怎樣的關系呢? 本文咱們來慢慢聊。

線程池原理

開始優化之前,我們先來看看線程池的實現原理,有助于你更好地理解后面的內容。

在 HotSpot VM 的線程模型中,Java 線程被一對一映射為內核線程。Java 在使用線程執行程序時,需要創建一個內核線程;當該 Java 線程被終止時,這個內核線程也會被回收。因此 Java 線程的創建與銷毀將會消耗一定的計算機資源,從而增加系統的性能開銷。

除此之外,大量創建線程同樣會給系統帶來性能問題,因為內存和 CPU 資源都將被線程搶占,如果處理不當,就會發生內存溢出、CPU 使用率超負荷等問題。

為了解決上述兩類問題,Java 提供了線程池概念,對于頻繁創建線程的業務場景,線程池可以創建固定的線程數量,并且在操作系統底層,輕量級進程將會把這些線程映射到內核。

線程池可以提高線程復用,又可以固定最大線程使用量,防止無限制地創建線程。

當程序提交一個任務需要一個線程時,會去線程池中查找是否有空閑的線程,若有,則直接使用線程池中的線程工作,若沒有,會去判斷當前已創建的線程數量是否超過最大線程數量,如未超過,則創建新線程,如已超過,則進行排隊等待或者直接拋出異常。

線程池框架 Executor

Java 最開始提供了 ThreadPool 實現了線程池,為了更好地實現用戶級的線程調度,更有效地幫助開發人員進行多線程開發,Java 提供了一套 Executor 框架。

這個框架中包括了 ScheduledThreadPoolExecutor 和 ThreadPoolExecutor 兩個核心線程池。前者是用來定時執行任務,后者是用來執行被提交的任務。

鑒于這兩個線程池的核心原理是一樣的,下面我們就重點看看 ThreadPoolExecutor 類是如何實現線程池的。

Executors 實現了以下四種類型的 ThreadPoolExecutor:

Executors 利用工廠模式實現的四種線程池,我們在使用的時候需要結合生產環境下的實際場景。

不過我不太推薦使用它們,因為選擇使用 Executors 提供的工廠類,將會忽略很多線程池的參數設置,工廠類一旦選擇設置默認參數,就很容易導致無法調優參數設置,從而產生性能問題或者資源浪費。

我建議你使用 ThreadPoolExecutor 自我定制一套線程池(阿里規范中也是建議不要使用Executors 創建線程池,建議使用ThreadPoolExecutor 來創建線程池)。

進入四種工廠類后,我們可以發現除了 newScheduledThreadPool 類,其它類均使用了 ThreadPoolExecutor 類進行實現,

你可以通過以下代碼簡單看下該方法

 

corePoolSize:線程池的核心線程數量

maximumPoolSize:線程池的最大線程數

keepAliveTime:當線程數大于核心線程數時,多余的空閑線程存活的最長時間

unit:時間單位

workQueue:任務隊列,用來儲存等待執行任務的隊列

threadFactory:線程工廠,用來創建線程,一般默認即可

handler:拒絕策略,當提交的任務過多而不能及時處理時,我們可以定制策略來處理任務

我們還可以通過下面這張圖來了解下線程池中各個參數的相互關系:

通過上圖,我們發現線程池有兩個線程數的設置,一個為核心線程數,一個為最大線程數。在創建完線程池之后,默認情況下,線程池中并沒有任何線程,等到有任務來才創建線程去執行任務。

但有一種情況排除在外,就是調用 prestartAllCoreThreads() 或者 prestartCoreThread() 方法的話,可以提前創建等于核心線程數的線程數量,這種方式被稱為預熱,在搶購系統中就經常被用到。

當創建的線程數等于 corePoolSize 時,提交的任務會被加入到設置的阻塞隊列中。當隊列滿了,會創建線程執行任務,直到線程池中的數量等于 maximumPoolSize。

當線程數量已經等于 maximumPoolSize 時, 新提交的任務無法加入到等待隊列,也無法創建非核心線程直接執行,我們又沒有為線程池設置拒絕策略,這時線程池就會拋出 RejectedExecutionException 異常,即線程池拒絕接受這個任務。

當線程池中創建的線程數量超過設置的 corePoolSize,在某些線程處理完任務后,如果等待 keepAliveTime 時間后仍然沒有新的任務分配給它,那么這個線程將會被回收。線程池回收線程時,會對所謂的“核心線程”和“非核心線程”一視同仁,直到線程池中線程的數量等于設置的 corePoolSize 參數,回收過程才會停止。

即使是 corePoolSize 線程,在一些非核心業務的線程池中,如果長時間地占用線程數量,也可能會影響到核心業務的線程池,這個時候就需要把沒有分配任務的線程回收掉。

我們可以通過 allowCoreThreadTimeOut 設置項要求線程池:將包括“核心線程”在內的,沒有任務分配的所有線程,在等待 keepAliveTime 時間后全部回收掉。

我們可以通過下面這張圖來了解下線程池的線程分配流程:

計算線程數量

了解完線程池的實現原理和框架,我們就可以動手實踐優化線程池的設置了。

我們知道,環境具有多變性,設置一個絕對精準的線程數其實是不大可能的,但我們可以通過一些實際操作因素來計算出一個合理的線程數,避免由于線程池設置不合理而導致的性能問題。下面我們就來看看具體的計算方法。

一般多線程執行的任務類型可以分為 CPU 密集型和 I/O 密集型,根據不同的任務類型,我們計算線程數的方法也不一樣。

CPU 密集型任務

這種任務消耗的主要是 CPU 資源,可以將線程數設置為 N(CPU 核心數)+1,比 CPU 核心數多出來的一個線程是為了防止線程偶發的缺頁中斷,或者其它原因導致的任務暫停而帶來的影響。

一旦任務暫停,CPU 就會處于空閑狀態,而在這種情況下多出來的一個線程就可以充分利用 CPU 的空閑時間。

下面我們用一個例子來驗證下這個方法的可行性,通過觀察 CPU 密集型任務在不同線程數下的性能情況就可以得出結果,你可以點擊Github下載到本地運行測試:  

  1. public class CPUTypeTest implements Runnable {    
  2.     // 整體執行時間,包括在隊列中等待的時間     
  3.     List<Long> wholeTimeList;     
  4.     // 真正執行時間     
  5.     List<Long> runTimeList;       
  6.     private long initStartTime = 0;     
  7.     /**  
  8.     * 構造函數  
  9.     * @param runTimeList  
  10.     * @param wholeTimeList  
  11.     */  
  12.     public CPUTypeTest(List<Long> runTimeList, List<Long> wholeTimeList) {  
  13.         initStartTime = System.currentTimeMillis();  
  14.         this.runTimeList = runTimeList; 
  15.         this.wholeTimeList = wholeTimeList; 
  16.     }  
  17.     /**  
  18.     * 判斷素數  
  19.     * @param number 
  20.     * @return  
  21.     */ 
  22.     public boolean isPrime(final int number) { 
  23.         if (number <= 1)  
  24.             return false;  
  25.         for (int i = 2; i <= Math.sqrt(number); i++) {  
  26.             if (number % i == 0)  
  27.                 return false;  
  28.         }  
  29.         return true;  
  30.     }  
  31.     /**  
  32.     * 計算素數  
  33.     * @param number  
  34.     * @return  
  35.     */ 
  36.     public int countPrimes(final int lower, final int upper) {  
  37.         int total = 0 
  38.         for (int i = lower; i <= upper; i++) {  
  39.             if (isPrime(i))  
  40.                 total++;  
  41.         }  
  42.         return total;  
  43.     }  
  44.     public void run() {  
  45.         long start = System.currentTimeMillis();  
  46.         countPrimes(1, 1000000);  
  47.         long end = System.currentTimeMillis();   
  48.         long wholeTime = end - initStartTime;  
  49.         long runTime = end - start;  
  50.         wholeTimeList.add(wholeTime);  
  51.         runTimeList.add(runTime);  
  52.         System.out.println(" 單個線程花費時間:" + (end - start));  
  53.     }  

測試代碼在 4 核 intel i5 CPU 機器上的運行時間變化如下:

綜上可知:當線程數量太小,同一時間大量請求將被阻塞在線程隊列中排隊等待執行線程,此時 CPU 沒有得到充分利用;當線程數量太大,被創建的執行線程同時在爭取 CPU 資源,又會導致大量的上下文切換,從而增加線程的執行時間,影響了整體執行效率。通過測試可知,4~6 個線程數是最合適的。

I/O 密集型任務

這種任務應用起來,系統會用大部分的時間來處理 I/O 交互,而線程在處理 I/O 的時間段內不會占用 CPU 來處理,這時就可以將 CPU 交出給其它線程使用。因此在 I/O 密集型任務的應用中,我們可以多配置一些線程,具體的計算方法是 2N。

這里我們還是通過一個例子來驗證下這個公式是否可以標準化:  

  1. public class IOTypeTest implements Runnable {     
  2.     // 整體執行時間,包括在隊列中等待的時間     
  3.     Vector<Long> wholeTimeList;   
  4.     // 真正執行時間     
  5.     Vector<Long> runTimeList;      
  6.     private long initStartTime = 0;   
  7.      /**   
  8.     * 構造函數    
  9.     * @param runTimeList     
  10.     * @param wholeTimeList    
  11.     */    
  12.     public IOTypeTest(Vector<Long> runTimeList, Vector<Long> wholeTimeList) {  
  13.          initStartTime = System.currentTimeMillis();   
  14.         this.runTimeList = runTimeList;   
  15.         this.wholeTimeList = wholeTimeList;   
  16.     }         
  17.     /**   
  18.     *IO 操作    
  19.     * @param number   
  20.     * @return     
  21.     * @throws IOException     
  22.     */    
  23.     public void readAndWrite() throws IOException {   
  24.         File sourceFile = new File("D:/test.txt");    
  25. // 創建輸入流      
  26. BufferedReader input = new BufferedReader(new FileReader(sourceFile));   
  27. // 讀取源文件, 寫入到新的文件     
  28. String line = null;   
  29. while((line = input.readLine()) != null){   
  30. //System.out.println(line);   
  31. }     
  32. // 關閉輸入輸出流    
  33. input.close();    
  34.     }         
  35.     public void run() {   
  36.         long start = System.currentTimeMillis();    
  37.         try {     
  38.             readAndWrite();   
  39.         } catch (IOException e) {   
  40.             // TODO Auto-generated catch block    
  41.             e.printStackTrace();      
  42.         }     
  43.         long end = System.currentTimeMillis();    
  44.          long wholeTime = end - initStartTime;     
  45.         long runTime = end - start;  
  46.          wholeTimeList.add(wholeTime);   
  47.          runTimeList.add(runTime);     
  48.         System.out.println(" 單個線程花費時間:" + (end - start));     
  49.     }     

備注:由于測試代碼讀取 2MB 大小的文件,涉及到大內存,所以在運行之前,我們需要調整 JVM 的堆內存空間:-Xms4g -Xmx4g,避免發生頻繁的 FullGC,影響測試結果。

通過測試結果,我們可以看到每個線程所花費的時間。當線程數量在 8 時,線程平均執行時間是最佳的,這個線程數量和我們的計算公式所得的結果就差不多。

看完以上兩種情況下的線程計算方法,你可能還想說,在平常的應用場景中,我們常常遇不到這兩種極端情況,那么碰上一些常規的業務操作,比如,通過一個線程池實現向用戶定時推送消息的業務,我們又該如何設置線程池的數量呢?

此時我們可以參考以下公式來計算線程數:

WT:線程等待時間

ST:線程時間運行時間

我們可以通過 JDK 自帶的工具 VisualVM 來查看 WT/ST 比例,以下例子是基于運行純 CPU 運算的例子,我們可以看到:  

  1. WT(線程等待時間)= 36788ms [線程運行總時間] - 36788ms[ST(線程時間運行時間)]= 0    
  2. 線程數 =N(CPU 核數)*(1+ 0 [WT(線程等待時間)]/36788ms[ST(線程時間運行時間)])= N(CPU 核數) 

這跟我們之前通過 CPU 密集型的計算公式 N+1 所得出的結果差不多。

綜合來看,我們可以根據自己的業務場景,從“N+1”和“2N”兩個公式中選出一個適合的,計算出一個大概的線程數量,之后通過實際壓測,逐漸往“增大線程數量”和“減小線程數量”這兩個方向調整,然后觀察整體的處理時間變化,最終確定一個具體的線程數量。

總結

本文我們主要學習了線程池的實現原理,Java 線程的創建和消耗會給系統帶來性能開銷,因此 Java 提供了線程池來復用線程,提高程序的并發效率。

Java 通過用戶線程與內核線程結合的 1:1 線程模型來實現,Java 將線程的調度和管理設置在了用戶態,提供了一套 Executor 框架來幫助開發人員提高效率。Executor 框架不僅包括了線程池的管理,還提供了線程工廠、隊列以及拒絕策略等,可以說 Executor 框架為并發編程提供了一個完善的架構體系。

在不同的業務場景以及不同配置的部署機器中,線程池的線程數量設置是不一樣的。

其設置不宜過大,也不宜過小,要根據具體情況,計算出一個大概的數值,再通過實際的性能測試,計算出一個合理的線程數量。

我們要提高線程池的處理能力,一定要先保證一個合理的線程數量,也就是保證 CPU 處理線程的最大化。在此前提下,我們再增大線程池隊列,通過隊列將來不及處理的線程緩存起來。在設置緩存隊列時,我們要盡量使用一個有界隊列,以防因隊列過大而導致的內存溢出問題 

 

責任編輯:龐桂玉 來源: Java后端技術
相關推薦

2022-03-04 10:17:04

Redis數據

2021-03-29 08:47:24

線程面試官線程池

2023-09-04 08:08:59

2020-09-08 10:56:55

Java多線程存儲器

2013-07-15 15:35:06

2009-03-12 10:52:43

Java線程多線程

2020-11-11 10:10:20

調用函數參數變量

2023-06-25 10:04:50

自動駕駛智能

2024-06-27 08:04:39

2019-09-09 09:50:27

設置Java線程池

2020-11-18 09:48:09

Synchronize多線程Java

2010-01-21 11:27:30

linux多線程機制線程同步

2009-06-29 17:49:47

Java多線程

2021-12-26 18:22:30

Java線程多線程

2022-03-08 22:21:55

網絡包隊列網卡

2015-12-22 10:39:52

Java多線程問題

2017-01-19 10:24:38

Java多線程問題

2023-12-07 07:28:25

線程共享資源

2013-07-16 10:12:14

iOS多線程多線程概念多線程入門

2023-06-05 07:56:10

線程分配處理器
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 亚洲精品一区二区三区蜜桃久 | 天天干夜夜拍 | 日韩精品在线观看视频 | 中文字幕二区三区 | 亚洲国产精品91 | 国产精品一区在线 | 成人免费观看视频 | 午夜一区二区三区在线观看 | 国产精品久久久久国产a级 欧美日本韩国一区二区 | 龙珠z在线观看 | 欧美乱大交xxxxx另类电影 | 美女二区| 久久成人18免费网站 | 天堂久久久久久久 | 麻豆一区一区三区四区 | 欧美性受xxxx白人性爽 | 91在线观看免费视频 | 狠狠亚洲 | 精品国产乱码一区二区三区 | 精品视频在线观看 | 黄色片视频免费 | wwwsihu| 亚洲国产精品久久久久 | 亚洲国产精品99久久久久久久久 | 婷婷成人在线 | 天天躁日日躁性色aⅴ电影 免费在线观看成年人视频 国产欧美精品 | 欧美激情精品久久久久 | 中文在线а√在线8 | 亚洲美女一区二区三区 | 色免费在线视频 | 久久久久久久久久久一区二区 | 欧美日韩三级视频 | 国产日韩欧美精品一区二区 | 日韩成人影院 | 中文字幕视频在线 | 亚洲免费三级 | 成人精品免费视频 | 97影院在线午夜 | 亚洲精品一区二区三区中文字幕 | 亚洲国产欧美在线人成 | 国产激情在线 |