線程池的核心知識就是:三大方法、7個參數、拒絕策略、優化配置
線程池原理
程序運行的本質是,佔用系統資源,CPU/磁盤網絡使用。我們希望可以高效的使用資源!池化技術就是不斷的演進出來的。
- 池化技術
簡單的說,池化技術就是提前準備一些資源,以供使用。
線程的創建和銷燬,以及數據庫的連接斷開都十分浪費資源。
只有是“池”,就會設計到兩個常量:
- minSize:最小容量,核心池子的大小
- maxSize最大容量
這些都是爲了彈性訪問,保證系統運行的效率。
舉一個常見的例子。去銀行取錢,一般來說銀行會固定開放2個窗口供人辦理業務。還有3個業務窗口,只有等到高峯期纔會開放使用。銀行裏提供了一個等待區(候客廳)有3個位置。當你去辦理業務時,前面有人正辦理,那你就需要坐在等待區,等待傳喚。
正常情況下,core大小:2
queue大小:3
maxSize: 5
最可以存在人數:maxSize+queue =8人
爲什麼要使用線程池
- 提高程序執行效率
- 控制線程的數量,防止程序崩潰
爲了減少創建和銷燬線程的次數,讓每個線程可以多次使用,可根據系統情況調整執行的線程數量,防止消耗過多內存,所以我們可以使用線程池.
Executor 介紹
java.util.concurrent.Executor: 大部分線程池相關的接口都是實現這個接口的。
public interface Executor {
/**
* Executes the given command at some time in the future. The command
* may execute in a new thread, in a pooled thread, or in the calling
* thread, at the discretion of the {@code Executor} implementation.
*
* @param command the runnable task
* @throws RejectedExecutionException if this task cannot be
* accepted for execution
* @throws NullPointerException if command is null
*/
void execute(Runnable command);
}
它的子接口和實現的類如下:
Executor接口的關係圖例(綠色實線箭頭是繼承,虛線是接口實現)
三大方法
數組有工具類Arrays,集合有工具類Collections,線程池同樣有工具類Executors。利用線程池工具類Executors就可以來創建線程池。
創建線程池的三大方法
ExecutorService threadpool1 = Executors.newFixedThreadPool(5); // 固定線程池大小
ExecutorService threadpool2 = Executors.newCachedThreadPool(); //可以彈性伸縮的線程池,遇強則強
ExecutorService threadpool3 = Executors.newSingleThreadExecutor(); // 只有一個
Executors.newFixedThreadPool(n)的使用
package com.jp.executorDemo;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* @className:
* @PackageName: com.jp.executorDemo
* @author: youjp
* @create: 2020-05-29 16:12
* @description: TODO:線程池的創建使用:1.創建固定大小的線程池,超過線程池大小部分,將拒絕
* @Version: 1.0
*/
public class ExecutorsDemo1 {
public static void main(String[] args) {
//1.創建線程池
ExecutorService threadPool= Executors.newFixedThreadPool(5); //固定大小,可自行設置
try {
for (int i = 0; i <10 ; i++) {
//2.使用線程池來執行線程
threadPool.execute(()->{
System.out.println("線程:"+Thread.currentThread().getName()+"執行任務");
});
}
} catch (Exception e) {
e.printStackTrace();
} finally {
//3.關閉線程池
threadPool.shutdown();
}
}
}
創建了容量大小爲5的線程池,遍歷10次去執行線程任務。會發現從始到終只有5個線程交替執行任務
Executors.newCachedThreadPool()的使用
這種線程池遇強則強,會彈性擴張,在實際的工作開發中不建議使用。
package com.jp.executorDemo;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* @className:
* @PackageName: com.jp.executorDemo
* @author: youjp
* @create: 2020-05-29 16:12
* @description: TODO:線程池的創建使用:2.創建彈性的線程池,能自動擴張
* @Version: 1.0
*/
public class ExecutorsDemo1 {
public static void main(String[] args) {
//1.創建線程池
ExecutorService threadPool= Executors.newCachedThreadPool();//彈性線程池,能自動擴張
try {
for (int i = 0; i <10 ; i++) {
//2.使用線程池來執行線程
threadPool.execute(()->{
System.out.println("線程:"+Thread.currentThread().getName()+"執行任務");
});
}
} catch (Exception e) {
e.printStackTrace();
} finally {
//3.關閉線程池
threadPool.shutdown();
}
}
}
有10個線程在執行任務,運行效果如下:
Executors.newSingleThreadExecutor()的使用
package com.jp.executorDemo;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* @className:
* @PackageName: com.jp.executorDemo
* @author: youjp
* @create: 2020-05-29 16:12
* @description: TODO:線程池的創建使用:3.創建單線程池,只有一個線程
* @Version: 1.0
*/
public class ExecutorsDemo1 {
public static void main(String[] args) {
//1.創建線程池
ExecutorService threadPool= Executors.newSingleThreadExecutor();//單線程池
try {
for (int i = 0; i <10 ; i++) {
//2.使用線程池來執行線程
threadPool.execute(()->{
System.out.println("線程:"+Thread.currentThread().getName()+"執行任務");
});
}
} catch (Exception e) {
e.printStackTrace();
} finally {
//3.關閉線程池
threadPool.shutdown();
}
}
}
只有一個線程在執行任務:
在實際的工作環境中,上述的幾種線程池創建方法都有很大的問題。禁止使用Executors去創建線程池。。我們要使用ThreadPoolExecutor 根據實際業務需要去自定義創建線程池。
阿里巴巴開發文檔有這樣寫到
使用Executors創建的線程池容易發生OOM(內存用盡). 因爲它允許的其你去隊列大小是integer最大值。
ThreadPoolExecutor 七大參數
在講 ThreadPoolExecutor 7大參數之前,我們先來分析一下Executors的三大方法創建線程池的底層代碼
//固定線程池大小
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
//創建彈性線程池
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
//創建單線程池
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
可以發現他們三個方法底層使用的都是去實例化一個ThreadPoolExecutor對象,設置了7個參數,這就是線程池創建的核心
public ThreadPoolExecutor(int corePoolSize, // 核心池子的大小
int maximumPoolSize, // 池子的最大大小
long keepAliveTime, // 空閒線程的保留時間
TimeUnit unit, // 時間單位
BlockingQueue<Runnable> workQueue, // 隊列
ThreadFactory threadFactory, // 線程工廠,不修改!用來創建線程
RejectedExecutionHandler handler // 拒絕策略) {
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.acc = System.getSecurityManager() == null ?
null :
AccessController.getContext();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
7個參數
- int corePoolSize, // 核心池子的大小
- int maximumPoolSize, // 池子的最大大小
- long keepAliveTime, // 空閒線程的保留時間,即在這個時間過後回收空閒的線程
- TimeUnit unit, // 時間單位
- BlockingQueue workQueue, // 隊列
- ThreadFactory threadFactory, // 線程工廠,不修改!用來創建線程
- RejectedExecutionHandler handler // 拒絕策略
既然瞭解到了線程池創建的核心,那麼我們就使用這個方法去實現之前銀行排隊辦理業務的案例。
總共8人來辦理業務,日常只開啓2個工作臺,有3個隊列位置,還有3個工作臺只有等隊列滿了,纔會開放。
package com.jp.executorDemo;
import java.util.concurrent.*;
/**
* @className:
* @PackageName: com.jp.executorDemo
* @author: youjp
* @create: 2020-05-29 16:12
* @description 描述:線程池7大參數的使用
* 1、只有隊列滿了,就會觸發最大線程池,否則永遠都只是corePoolSize個線程在運行,所以,隊列大小一定要根據業務情況進行設置;
* 2、當請求線程超過線程池(maximumPoolSize + workQueue),就會觸發拒絕策略,至於怎麼拒絕,與拒絕策略RejectedExecutionHandler有關。
*/
public class ExecutorsDemo2 {
public static void main(String[] args) {
//1.創建線程池
ExecutorService threadPool = new ThreadPoolExecutor(
2, // 核心池子的大小,代表核心的2個工作臺
5, // 線程池最大大小5,代表最大可開啓的工作臺
2L, // 空閒線程的保留時間
TimeUnit.SECONDS, // 超時回收空閒的線程
new LinkedBlockingDeque<>(3), // *根據業務設置隊列大小,隊列大小一定要設置*
Executors.defaultThreadFactory(), // 不用變
new ThreadPoolExecutor.AbortPolicy () //拒絕策略,這裏使用的是默認的測了:隊列滿了,就丟棄任務拋出異常!
);
try {
// 隊列 RejectedExecutionException 拒絕策略
for (int i = 1; i <= 8; i++) { //8個人
// 默認在處理
threadPool.execute(()->{
System.out.println(Thread.currentThread().getName()+" running....");
});
}
} catch (Exception e) {
e.printStackTrace();
} finally {
threadPool.shutdown();
}
}
}
因爲辦理業務是8人,核心工作臺爲2個,隊列只有3個位置,隊列已經排滿,所以會觸發最多線程池,開啓5個工作臺。
那如果共有9個人來辦理業務,而我們設置最多隻能存8人,會出現怎麼樣的結果呢?
只要超過線程池大小就會觸發拒絕策略
四種拒絕策略
AbortPolicy (默認的:隊列滿了,就丟棄任務拋出異常!);
CallerRunsPolicy(哪來的回哪去? 誰叫你來的,你就去哪裏處理);
DiscardOldestPolicy (嘗試將最早進入隊列的任務刪除,嘗試加入新任務);
DiscardPolicy (隊列滿了任務也會丟棄,不拋出異常)。
AbortPolicy
使用該策略,當請求線程超過線程池(maximumPoolSize + workQueue)就會丟棄任務拋出異常
package com.jp.executorDemo;
import java.util.concurrent.*;
/**
* @className:
* @PackageName: com.jp.executorDemo
* @author: youjp
* @create: 2020-05-29 16:12
* @description 描述:線程池7大參數的使用
* 1、隊列滿了,就會觸發最大線程池,否則永遠都只是corePoolSize個線程在運行,所以,隊列大小一定要根據業務情況進行設置;
* 2、當請求線程超過線程池(maximumPoolSize + workQueue),就會觸發拒絕策略,至於怎麼拒絕,與拒絕策略RejectedExecutionHandler有關。
*/
public class ExecutorsDemo2 {
public static void main(String[] args) {
//1.創建線程池
ExecutorService threadPool = new ThreadPoolExecutor(
2, // 核心池子的大小
5, // 線程池最大大小5
2L, // 空閒線程的保留時間
TimeUnit.SECONDS, // 超時回收空閒的線程
new LinkedBlockingDeque<>(3), // *根據業務設置隊列大小,隊列大小一定要設置*
Executors.defaultThreadFactory(), // 不用變
new ThreadPoolExecutor.AbortPolicy () //拒絕策略
);
try {
// 隊列 RejectedExecutionException 拒絕策略
for (int i = 1; i <= 9; i++) { // 9個人
// 默認在處理
threadPool.execute(()->{
System.out.println(Thread.currentThread().getName()+" running....");
});
}
} catch (Exception e) {
e.printStackTrace();
} finally {
threadPool.shutdown();
}
}
}
我們把人數設置爲9,在執行測試
CallerRunsPolicy
使用該策略,當請求線程超過線程池(maximumPoolSize + workQueue)就會哪裏的會哪去執行。
package com.jp.executorDemo;
import java.util.concurrent.*;
/**
* @className:
* @PackageName: com.jp.executorDemo
* @author: youjp
* @create: 2020-05-29 16:12
* @description 描述:線程池7大參數的使用
* 1、隊列滿了,就會觸發最大線程池,否則永遠都只是corePoolSize個線程在運行,所以,隊列大小一定要根據業務情況進行設置;
* 2、當請求線程超過線程池(maximumPoolSize + workQueue),就會觸發拒絕策略,至於怎麼拒絕,與拒絕策略RejectedExecutionHandler有關。
*/
public class ExecutorsDemo2 {
public static void main(String[] args) {
//1.創建線程池
ExecutorService threadPool = new ThreadPoolExecutor(
2, // 核心池子的大小
5, // 線程池最大大小5
2L, // 空閒線程的保留時間
TimeUnit.SECONDS, // 超時回收空閒的線程
new LinkedBlockingDeque<>(3), // *根據業務設置隊列大小,隊列大小一定要設置*
Executors.defaultThreadFactory(), // 不用變
new ThreadPoolExecutor.CallerRunsPolicy () //拒絕策略
);
try {
// 隊列 RejectedExecutionException 拒絕策略
for (int i = 1; i <= 9; i++) { // 9個人
// 默認在處理
threadPool.execute(()->{
System.out.println(Thread.currentThread().getName()+" running....");
});
}
} catch (Exception e) {
e.printStackTrace();
} finally {
threadPool.shutdown();
}
}
}
執行結果
DiscardOldestPolicy
使用該策略,當請求線程超過線程池(maximumPoolSize + workQueue)就會嘗試將最早進入隊列的任務刪除,嘗試加入新任務.
package com.jp.executorDemo;
import java.util.concurrent.*;
/**
* @className:
* @PackageName: com.jp.executorDemo
* @author: youjp
* @create: 2020-05-29 16:12
* @description 描述:線程池7大參數的使用
* 1、隊列滿了,就會觸發最大線程池,否則永遠都只是corePoolSize個線程在運行,所以,隊列大小一定要根據業務情況進行設置;
* 2、當請求線程超過線程池(maximumPoolSize + workQueue),就會觸發拒絕策略,至於怎麼拒絕,與拒絕策略RejectedExecutionHandler有關。
*/
public class ExecutorsDemo2 {
public static void main(String[] args) {
//1.創建線程池
ExecutorService threadPool = new ThreadPoolExecutor(
2, // 核心池子的大小
5, // 線程池最大大小5
2L, // 空閒線程的保留時間
TimeUnit.SECONDS, // 超時回收空閒的線程
new LinkedBlockingDeque<>(3), // *根據業務設置隊列大小,隊列大小一定要設置*
Executors.defaultThreadFactory(), // 不用變
new ThreadPoolExecutor.DiscardOldestPolicy () //拒絕策略
);
try {
// 隊列 RejectedExecutionException 拒絕策略
for (int i = 1; i <=15; i++) { // 15個人
// 默認在處理
final int temp=i;
threadPool.execute(()->{
System.out.println(Thread.currentThread().getName()+" running...."+temp);
});
}
} catch (Exception e) {
e.printStackTrace();
} finally {
threadPool.shutdown();
}
}
}
執行結果
DiscardPolicy
使用該策略,當請求線程超過線程池(maximumPoolSize + workQueue)隊列滿了,後面的任務便會丟棄,不拋出異常
package com.jp.executorDemo;
import java.util.concurrent.*;
/**
* @className:
* @PackageName: com.jp.executorDemo
* @author: youjp
* @create: 2020-05-29 16:12
* @description 描述:線程池7大參數的使用
* 1、隊列滿了,就會觸發最大線程池,否則永遠都只是corePoolSize個線程在運行,所以,隊列大小一定要根據業務情況進行設置;
* 2、當請求線程超過線程池(maximumPoolSize + workQueue),就會觸發拒絕策略,至於怎麼拒絕,與拒絕策略RejectedExecutionHandler有關。
*/
public class ExecutorsDemo2 {
public static void main(String[] args) {
//1.創建線程池
ExecutorService threadPool = new ThreadPoolExecutor(
2, // 核心池子的大小
5, // 線程池最大大小5
2L, // 空閒線程的保留時間
TimeUnit.SECONDS, // 超時回收空閒的線程
new LinkedBlockingDeque<>(3), // *根據業務設置隊列大小,隊列大小一定要設置*
Executors.defaultThreadFactory(), // 不用變
new ThreadPoolExecutor.DiscardPolicy () //拒絕策略
);
try {
// 隊列 RejectedExecutionException 拒絕策略
for (int i = 1; i <=15; i++) { // 15個人
// 默認在處理
final int temp=i;
threadPool.execute(()->{
System.out.println(Thread.currentThread().getName()+" running...."+temp);
});
}
} catch (Exception e) {
e.printStackTrace();
} finally {
threadPool.shutdown();
}
}
}
線程池實現原理
提交一個任務到線程池中,線程池的處理流程如下:
1、判斷線程池裏的核心線程是否都在執行任務,如果不是(核心線程空閒或者還有核心線程沒有被創建)則創建一個新的工作線程來執行任務。如果核心線程都在執行任務,則進入下個流程。
2、線程池判斷工作隊列是否已滿,如果工作隊列沒有滿,則將新提交的任務存儲在這個工作隊列裏。如果工作隊列滿了,則進入下個流程。
3、判斷線程池裏的線程是否都處於工作狀態,如果沒有,則創建一個新的工作線程來執行任務。如果已經滿了,則交給飽和策略來處理這個任務。
優化配置
在工作中,我們應該如何合理的設置線程池的參數呢?通常我們會從2個方面去考慮
- CPU 密集型
- IO 密集型
CPU密集型就是根據最大能支持多少個線程同時跑,一般將線程池的maximumPoolSize(最大線程池) 參數設置與CPU處理器一樣大就可以了。可以通過如下方法獲取到服務器運行環境的CPU個數:
Runtime.getRuntime.availableProcessors(); //獲取到所處運行環境的CPU個數
完整代碼
package com.jp.executorDemo;
import java.util.concurrent.*;
/**
* @className:
* @PackageName: com.jp.executorDemo
* @author: youjp
* @create: 2020-05-29 16:12
* @description 描述:線程池7大參數的使用
* 1、隊列滿了,就會觸發最大線程池,否則永遠都只是corePoolSize個線程在運行,所以,隊列大小一定要根據業務情況進行設置;
* 2、當請求線程超過線程池(maximumPoolSize + workQueue),就會觸發拒絕策略,至於怎麼拒絕,與拒絕策略RejectedExecutionHandler有關。
*/
public class ExecutorsDemo2 {
public static void main(String[] args) {
//1.創建線程池
ExecutorService threadPool = new ThreadPoolExecutor(
2, // 核心池子的大小
Runtime.getRuntime().availableProcessors(), // 線程池最大數
2L, // 空閒線程的保留時間
TimeUnit.SECONDS, // 超時回收空閒的線程
new LinkedBlockingDeque<>(3), // *根據業務設置隊列大小,隊列大小一定要設置*
Executors.defaultThreadFactory(), // 不用變
new ThreadPoolExecutor.DiscardOldestPolicy () //拒絕策略
);
try {
// 隊列 RejectedExecutionException 拒絕策略
for (int i = 1; i <=15; i++) { // 15個人
// 默認在處理
final int temp=i;
threadPool.execute(()->{
System.out.println(Thread.currentThread().getName()+" running...."+temp);
});
}
} catch (Exception e) {
e.printStackTrace();
} finally {
threadPool.shutdown();
}
}
}
IO 密集型從磁盤讀寫、 一個線程在IO操作的時候、另外一個線程在CPU中跑,造成CPU空閒。最大線程數應該設置爲 IO任務數! 對於大文件的讀寫非常耗時,我們應該用單獨的線程讓他慢慢跑。