Java 中線程池創建的幾種方式
首先我們要先知道 Java 中創建線程池的方式,java中創建線程池的方式一般有兩種,如下所示:
- 通過Executors工廠方法創建
- 通過new ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue)自定義創建
Executors 工廠方法創建
上代碼:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
|
package com.base.demo.design.play; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; /** * @Description: 線程池代碼 * @BelongsProject: base-demo-design * @BelongsPackage: com.base.demo.design.play * @Author: ChenYongJia * @CreateTime: 2021-08-14 15:26 * @Email: chen87647213@163.com * @Version: 1.0 */ public class TestThreadPoolExecutor { public static void main(String[] args) { // 創建使用單個線程的線程池 ExecutorService es1 = Executors.newSingleThreadExecutor(); for ( int i = 0 ; i < 10 ; i++) { es1.submit( new Runnable() { @Override public void run() { System.out.println(Thread.currentThread().getName() + "正在執行任務" ); } }); } // 創建使用固定線程數的線程池 ExecutorService es2 = Executors.newFixedThreadPool( 3 ); for ( int i = 0 ; i < 10 ; i++) { es2.submit( new Runnable() { @Override public void run() { System.out.println(Thread.currentThread().getName() + "正在執行任務" ); } }); } // 創建一個會根據需要創建新線程的線程池 ExecutorService es3 = Executors.newCachedThreadPool(); for ( int i = 0 ; i < 20 ; i++) { es3.submit( new Runnable() { @Override public void run() { System.out.println(Thread.currentThread().getName() + "正在執行任務" ); } }); } // 創建擁有固定線程數量的定時線程任務的線程池 ScheduledExecutorService es4 = Executors.newScheduledThreadPool( 2 ); System.out.println( "時間:" + System.currentTimeMillis()); for ( int i = 0 ; i < 5 ; i++) { es4.schedule( new Runnable() { @Override public void run() { System.out.println( "時間:" +System.currentTimeMillis()+ "--" +Thread.currentThread().getName() + "正在執行任務" ); } }, 3 , TimeUnit.SECONDS); } // 創建只有一個線程的定時線程任務的線程池 ScheduledExecutorService es5 = Executors.newSingleThreadScheduledExecutor(); System.out.println( "時間:" + System.currentTimeMillis()); for ( int i = 0 ; i < 5 ; i++) { es5.schedule( new Runnable() { @Override public void run() { System.out.println( "時間:" +System.currentTimeMillis()+ "--" +Thread.currentThread().getName() + "正在執行任務" ); } }, 3 , TimeUnit.SECONDS); } } } |
運行結果如下:
pool-1-thread-1正在執行任務
pool-1-thread-1正在執行任務
pool-1-thread-1正在執行任務
pool-1-thread-1正在執行任務
pool-1-thread-1正在執行任務
pool-1-thread-1正在執行任務
pool-1-thread-1正在執行任務
pool-1-thread-1正在執行任務
pool-1-thread-1正在執行任務
pool-1-thread-1正在執行任務
pool-2-thread-1正在執行任務
pool-2-thread-2正在執行任務
pool-2-thread-1正在執行任務
pool-2-thread-3正在執行任務
pool-2-thread-2正在執行任務
pool-2-thread-3正在執行任務
pool-2-thread-1正在執行任務
pool-2-thread-3正在執行任務
pool-2-thread-2正在執行任務
pool-2-thread-1正在執行任務
pool-3-thread-1正在執行任務
pool-3-thread-2正在執行任務
pool-3-thread-2正在執行任務
pool-3-thread-3正在執行任務
pool-3-thread-1正在執行任務
pool-3-thread-3正在執行任務
pool-3-thread-4正在執行任務
pool-3-thread-1正在執行任務
pool-3-thread-3正在執行任務
pool-3-thread-4正在執行任務
pool-3-thread-5正在執行任務
pool-3-thread-4正在執行任務
pool-3-thread-6正在執行任務
pool-3-thread-7正在執行任務
pool-3-thread-8正在執行任務
pool-3-thread-9正在執行任務
pool-3-thread-2正在執行任務
pool-3-thread-6正在執行任務
pool-3-thread-1正在執行任務
pool-3-thread-3正在執行任務
時間:1628926041159
時間:1628926041160
時間:1628926044172--pool-5-thread-1正在執行任務
時間:1628926044172--pool-4-thread-2正在執行任務
時間:1628926044172--pool-4-thread-1正在執行任務
時間:1628926044172--pool-4-thread-2正在執行任務
時間:1628926044172--pool-5-thread-1正在執行任務
時間:1628926044172--pool-4-thread-2正在執行任務
時間:1628926044172--pool-4-thread-1正在執行任務
時間:1628926044172--pool-5-thread-1正在執行任務
時間:1628926044172--pool-5-thread-1正在執行任務
時間:1628926044172--pool-5-thread-1正在執行任務
new ThreadPoolExecutor() 自定義創建
1
2
3
4
5
6
7
|
public ThreadPoolExecutor( int corePoolSize, //線程池的核心線程數量 int maximumPoolSize, //線程池的最大線程數 long keepAliveTime, //當線程數大于核心線程數時,多余的空閑線程存活的最長時間 TimeUnit unit, //時間單位 BlockingQueue<Runnable> workQueue, //任務隊列,用來儲存等待執行任務的隊列 ThreadFactory threadFactory, //線程工廠,用來創建線程,一般默認即可 RejectedExecutionHandler handler) //拒絕策略,當提交的任務過多而不能及時處理時,我們可以定制策略來處理任務 |
- corePoolSize:核心池的大小,這個參數跟后面講述的線程池的實現原理有非常大的關系。在創建了線程池后,默認情況下,線程池中并沒有任何線程,而是等待有任務到來才創建線程去執行任務,除非調用了 prestartAllCoreThreads() 或者 prestartCoreThread() 方法,從這2個方法的名字就可以看出,是預創建線程的意思,即在沒有任務到來之前就創建 corePoolSize 個線程或者一個線程。默認情況下,在創建了線程池后,線程池中的線程數為 0,當有任務來之后,就會創建一個線程去執行任務,當線程池中的線程數目達到 corePoolSize 后,就會把到達的任務放到緩存隊列當中;
- maximumPoolSize:線程池最大線程數,這個參數也是一個非常重要的參數,它表示在線程池中最多能創建多少個線程;
- keepAliveTime:表示線程沒有任務執行時最多保持多久時間會終止。默認情況下,只有當線程池中的線程數大于 corePoolSize 時,keepAliveTime 才會起作用,直到線程池中的線程數不大于 corePoolSize,即當線程池中的線程數大于 corePoolSize 時,如果一個線程空閑的時間達到 keepAliveTime,則會終止,直到線程池中的線程數不超過 corePoolSize。但是如果調用了 allowCoreThreadTimeOut(boolean) 方法,在線程池中的線程數不大于 corePoolSize 時,keepAliveTime 參數也會起作用,直到線程池中的線程數為 0;
- unit:參數 keepAliveTime 的時間單位,有7種取值,在 TimeUnit 類中有7種靜態屬性,如下所示:
1
2
3
4
5
6
7
|
TimeUnit.DAYS; //天 TimeUnit.HOURS; //小時 TimeUnit.MINUTES; //分鐘 TimeUnit.SECONDS; //秒 TimeUnit.MILLISECONDS; //毫秒 TimeUnit.MICROSECONDS; //微妙 TimeUnit.NANOSECONDS; //納秒 |
-
workQueue:阻塞隊列。保存等待執行的任務的阻塞隊列,當提交一個新的任務到線程池以后, 線程池會根據當前線程池中正在運行著的線程的數量來決定對該任務的處理方式,主要有以下幾種處理方式:
- 直接切換:這種方式常用的隊列是 SynchronousQueue ,不進行任務存儲,直接執行;
- 使用無界隊列:一般使用基于鏈表的阻塞隊列 LinkedBlockingQueue。如果使用這種方式,那么線程池中能夠創建的最大線程數就是 corePoolSize ,而 maximumPoolSize 就不會起作用了(因為是無界的)。當線程池中所有的核心線程都是 RUNNING狀態 時,這時一個新的任務提交就會放入等待隊列中。
- 使用有界隊列:一般使用 ArrayBlockingQueue 。使用該方式可以將線程池的最大線程數量限制為 maximumPoolSize ,這樣能夠降低資源的消耗,但同時這種方式也使得線程池對線程的調度變得更困難,因為線程池和隊列的容量都是有限的值,所以要想使線程池處理任務的吞吐率達到一個相對合理的范圍,又想使線程調度相對簡單,并且還要盡可能的降低線程池對資源的消耗,就需要合理的設置這兩個數量。
- PS:ArrayBlockingQueue 和 PriorityBlockingQueue 使用較少,一般使用 LinkedBlockingQueue 和 SynchronousQueue。線程池的排隊策略與 BlockingQueue 有關。
- threadFactory:用于設置創建線程的工廠,可以通過線程工廠給每個創建出來的線程做些更有意義的事情,比如設置 daemon 和 優先級 等等
- handler:飽和策略。當線程池的阻塞隊列已滿和指定的線程都已經開啟,說明當前線程池已經處于飽和狀態了,那么就需要采用一種策略來處理這種情況。表示當拒絕處理任務時的策略,有以下幾種取值(也可以根據應用場景需要來實現 RejectedExecutionHandler 接口自定義策略。如記錄日志或持久化不能處理的任務。):
AbortPolicy:默認的拒絕策略,直接拋出異常。`throws RejectedExecutionException`。
CallerRunsPolicy:只用調用者所在線程來運行任務(提交任務的線程自己去執行該任務)。
DiscardOldestPolicy:丟棄最老的任務,其實就是把最早進入工作隊列的任務丟棄,然后把新任務加入到工作隊列。
DiscardPolicy:不處理,直接丟棄任務,沒有任何異常拋出。
執行流程:
- 線程池創建線程,會判斷當前線程數是否大于 corePoolSize。
- 如果大于則存在緩存隊列,緩沖隊列存滿后會繼續創建線程直到 maximumPoolSize ,拋出拒絕的異常。
- 如果小于則創建線程,執行任務,執行完后會從緩存隊列中取任務再執行
創建多少線程合適
一般多線程執行的任務類型可以分為 CPU 密集型 和 I/O 密集型,根據不同的任務類型,我們計算線程數的方法也不一樣。創建多少線程合適,要看多線程具體的應用場景。我們的程序一般都是 CPU 計算 和 I/O 操作交叉執行 的,由于 I/O 設備 的速度相對于 CPU 來說都很慢,所以大部分情況下,I/O 操作執行的時間相對于 CPU 計算來說都非常長 ,這種場景我們一般都稱為 I/O 密集型計算 和 I/O 密集型計算 相對的就是 CPU 密集型計算,CPU 密集型計算 大部分場景下都是 純 CPU 計算。
- CPU 密集型任務:多線程主要目的是提成CPU利用率,保持和CPU核數一致即可。可以將線程數設置為 N(CPU 核心數)+1 ,比 CPU 核心數 多出來的一個線程是 為了防止線程偶發的缺頁中斷,或者其它原因導致的 任務暫停 而帶來的影響。一旦 任務暫停 ,CPU 就會處于 空閑狀態 ,而在這種情況下多出來的一個線程就可以充分利用 CPU 的空閑時間。
- 測試代碼如下,結果還是自己親力親為吧實踐出真知
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
|
package com.base.demo.design.play; import java.util.List; /** * @Description: CPU測試 * @BelongsProject: base-demo-design * @BelongsPackage: com.base.demo.design.play * @Author: ChenYongJia * @CreateTime: 2021-08-14 16:13 * @Email: chen87647213@163.com * @Version: 1.0 */ public class CPUTypeTest implements Runnable { /** * 整體執行時間,包括在隊列中等待的時間 */ List<Long> wholeTimeList; /** * 真正執行時間 */ List<Long> runTimeList; private long initStartTime = 0 ; /** * 構造函數 * * @param runTimeList * @param wholeTimeList * @return * @date 2021/8/14 16:13 * @author ChenYongJia * @version 1.0 */ public CPUTypeTest(List<Long> runTimeList, List<Long> wholeTimeList) { initStartTime = System.currentTimeMillis(); this .runTimeList = runTimeList; this .wholeTimeList = wholeTimeList; } /** * 判斷素數 * * @param number * @return boolean * @date 2021/8/14 16:13 * @author ChenYongJia * @version 1.0 */ public boolean isPrime( final int number) { if (number <= 1 ) return false ; for ( int i = 2 ; i <= Math.sqrt(number); i++) { if (number % i == 0 ) return false ; } return true ; } /** * 計算素數 * * @param lower * @param upper * @return int * @date 2021/8/14 16:14 * @author ChenYongJia * @version 1.0 */ public int countPrimes( final int lower, final int upper) { int total = 0 ; for ( int i = lower; i <= upper; i++) { if (isPrime(i)) total++; } return total; } @Override public void run() { long start = System.currentTimeMillis(); countPrimes( 1 , 1000000 ); long end = System.currentTimeMillis(); long wholeTime = end - initStartTime; long runTime = end - start; wholeTimeList.add(wholeTime); runTimeList.add(runTime); System.out.println( "單個線程花費時間:" + (end - start)); } } |
I/O 密集型任務:這種任務應用起來,系統會用大部分的時間來處理 I/O 交互,而線程在處理 I/O 的時間段內不會占用 CPU 來處理,這時就可以將 CPU 交出給其它線程使用。因此在 I/O 密集型任務的應用中,我們可以多配置一些線程,具體的計算方法是 2N(CPU 核心數)。
- 一般最佳線程數目 = (線程等待時間與線程CPU時間之比 + 1)* CPU數目
- 實戰:實際需要根據上線情況進行調整優化
- 測試代碼如下,結果還是自己親力親為吧實踐出真知
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
|
package com.base.demo.design.play; import java.io.BufferedReader; import java.io.File; import java.io.FileReader; import java.io.IOException; import java.util.Vector; /** * @Description: IO測試 * @BelongsProject: base-demo-design * @BelongsPackage: com.base.demo.design.play * @Author: ChenYongJia * @CreateTime: 2021-08-14 16:18 * @Email: chen87647213@163.com * @Version: 1.0 */ public class IOTypeTest implements Runnable { /** * 整體執行時間,包括在隊列中等待的時間 */ Vector<Long> wholeTimeList; /** * 真正執行時間 */ Vector<Long> runTimeList; private long initStartTime = 0 ; /** * 構造函數 * * @param runTimeList * @param wholeTimeList */ public IOTypeTest(Vector<Long> runTimeList, Vector<Long> wholeTimeList) { initStartTime = System.currentTimeMillis(); this .runTimeList = runTimeList; this .wholeTimeList = wholeTimeList; } /** * IO操作 * * @return void * @date 2021/8/14 16:18 * @author ChenYongJia * @version 1.0 */ public void readAndWrite() throws IOException { File sourceFile = new File( "D:/test.txt" ); //創建輸入流 BufferedReader input = new BufferedReader( new FileReader(sourceFile)); //讀取源文件,寫入到新的文件 String line = null ; while ((line = input.readLine()) != null ) { //System.out.println(line); } //關閉輸入輸出流 input.close(); } @Override public void run() { long start = System.currentTimeMillis(); try { readAndWrite(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } long end = System.currentTimeMillis(); long wholeTime = end - initStartTime; long runTime = end - start; wholeTimeList.add(wholeTime); runTimeList.add(runTime); System.out.println( "單個線程花費時間:" + (end - start)); } } |
喜歡折騰的同學可以試試(我貼出來的兩段 TEST代碼):在不同線程數的情況下,run 方法運行時間的差異。可以通過創建不同數量的線程,線程中 new 該 Test 對象(new 兩個 List 傳到構造參數里)并提交到線程池。查看并歸納計算得出結果。
附:線程池原理
學習線程池的實現原理,有助于你更好地理解內容。
在 HotSpot VM 的線程模型中,Java 線程被一對一映射為內核線程。Java 在使用線程執行程序時,需要創建一個內核線程;當該 Java 線程被終止時,這個內核線程也會被回收。因此 Java 線程的創建與銷毀將會消耗一定的計算機資源,從而增加系統的性能開銷。
除此之外,大量創建線程同樣會給系統帶來性能問題,因為內存和 CPU 資源都將被線程搶占,如果處理不當,就會發生內存溢出、CPU 使用率超負荷等問題。
為了解決上述兩類問題,Java 提供了線程池概念,對于頻繁創建線程的業務場景,線程池可以創建固定的線程數量,并且在操作系統底層,輕量級進程將會把這些線程映射到內核。
線程池可以提高線程復用,又可以固定最大線程使用量,防止無限制地創建線程。當程序提交一個任務需要一個線程時,會去線程池中查找是否有空閑的線程,若有,則直接使用線程池中的線程工作,若沒有,會去判斷當前已創建的線程數量是否超過最大線程數量,如未超過,則創建新線程,如已超過,則進行排隊等待或者直接拋出異常。
最后
在不同的業務場景以及不同配置的部署機器中,線程池的線程數量設置是不一樣的。其設置不宜過大,也不宜過小,要根據具體情況,計算出一個大概的數值,再通過實際的性能測試,計算出一個合理的線程數量。
到此這篇關于Java線程池大小設置的文章就介紹到這了,更多相關Java線程池大小設置內容請搜索服務器之家以前的文章或繼續瀏覽下面的相關文章希望大家以后多多支持服務器之家!
原文鏈接:https://juejin.cn/post/6997566049155547166