開發中經常會遇到各種池(如:連接池,線程池),它們的作用就是爲了提高性能及減少開銷,在
JDK1.5
以後的java.util.concurrent
包中內置了很多不同使用場景的線程池
,爲了更好的理解它們,自己手寫一個線程池,加深印象。
概述
1.什麼是池
它的基本思想是一種對象池
,程序初始化的時候開闢一塊內存空間,裏面存放若干個線程對象
,池中線程執行調度由池管理器來處理。當有線程任務時,從池中取一個,執行完成後線程對象歸池,這樣可以避免反覆創建線程對象
所帶來的性能開銷,節省系統的資源。
2.使用線程池的好處
合理的使用線程池
可以重複利用已創建的線程,這樣就可以減少在創建線程和銷燬線程上花費的時間和資源。並且,線程池在某些情況下還能動態調整
工作線程的數量,以平衡資源消耗和工作效率。同時線程池還提供了對池中工作線程進行統一的管理的相關方法。這樣就相當於我們一次創建,就可以多次使用,大量的節省了系統頻繁的創建和銷燬線程所需要的資源。
簡易版實現
包含功能:
1.創建線程池,銷燬線程池,添加新任務
2.沒有任務進入等待,有任務則處理掉
3.動態伸縮,擴容
4.拒絕策略
介紹了線程池的原理以及主要組件之後,就讓我們來手動實現一個自己的線程池,以加深理解和深入學習。因爲自己實現的簡易版本
所以不建議生產中使用,生產中使用java.util.concurrent
會更加健壯和優雅(後續文章會介紹)
代碼
以下線程池相關代碼均在SimpleThreadPoolExecutor.java
中,由於爲了便於解讀因此以代碼塊的形式呈現
維護一個內部枚舉類,用來標記當前
任務線程
狀態,在Thread
中其實也有.
private enum TaskState {
FREE, RUNNABLE, BLOCKED, TERMINATED;
}
定義拒絕策略接口,以及默認實現
static class DiscardException extends RuntimeException {
private static final long serialVersionUID = 8827362380544575914L;
DiscardException(String message) {
super(message);
}
}
interface DiscardPolicy {//拒絕策略接口
void discard() throws DiscardException;
}
任務線程具體實現
1.繼承Thread
,重寫run方法。
2.this.taskState == TaskState.FREE && TASK_QUEUE.isEmpty()
如果當前線程處於空閒狀態且沒有任何任務了就將它wait
住,讓出CPU執行權
3.如果有任務就去執行FIFO(先進先出)策略
4.定義close
方法,關閉線程,當然這裏不能暴力關閉,所以這裏有需要藉助interrupt
public static class WorkerTask extends Thread {
// 線程狀態
private TaskState taskState;
// 線程編號
private static int threadInitNumber;
/**
* 生成線程名,參考Thread.nextThreadNum();
*
* @return
*/
private static synchronized String nextThreadName() {
return THREAD_NAME_PREFIX + (++threadInitNumber);
}
WorkerTask() {
super(THREAD_GROUP, nextThreadName());
}
@Override
public void run() {
Runnable target;
//說明該線程處於空閒狀態
OUTER:
while (this.taskState != TaskState.TERMINATED) {
synchronized (TASK_QUEUE) {
while (this.taskState == TaskState.FREE && TASK_QUEUE.isEmpty()) {
try {
this.taskState = TaskState.BLOCKED;//此處標記
//沒有任務就wait住,讓出CPU執行權
TASK_QUEUE.wait();
//如果被打斷說明當前線程執行了 shutdown() 方法 線程狀態爲 TERMINATED 直接跳到 while 便於退出
} catch (InterruptedException e) {
break OUTER;
}
}
target = TASK_QUEUE.removeFirst();//遵循FIFO策略
}
if (target != null) {
this.taskState = TaskState.RUNNABLE;
target.run();//開始任務了
this.taskState = TaskState.FREE;
}
}
}
void close() {//優雅關閉線程
this.taskState = TaskState.TERMINATED;
this.interrupt();
}
}
簡易版線程池,主要就是維護了一個
任務隊列
和線程集
,爲了動態擴容,自己也繼承了Thread
去做監聽操作,對外提供submit()提交執行任務
、shutdown()等待所有任務工作完畢,關閉線程池
public class SimpleThreadPoolExecutor extends Thread {
// 線程池大小
private int threadPoolSize;
// 最大接收任務
private int queueSize;
// 拒絕策略
private DiscardPolicy discardPolicy;
// 是否被銷燬
private volatile boolean destroy = false;
private final static int DEFAULT_MIN_THREAD_SIZE = 2;// 默認最小線程數
private final static int DEFAULT_ACTIVE_THREAD_SIZE = 5;// 活躍線程
private final static int DEFAULT_MAX_THREAD_SIZE = 10;// 最大線程
private final static int DEFAULT_WORKER_QUEUE_SIZE = 100;// 最多執行多少任務
private final static String THREAD_NAME_PREFIX = "MY-THREAD-NAME-";//線程名前綴
private final static String THREAD_POOL_NAME = "SIMPLE-POOL";//線程組的名稱
private final static ThreadGroup THREAD_GROUP = new ThreadGroup(THREAD_POOL_NAME);//線程組
private final static List<WorkerTask> WORKER_TASKS = new ArrayList<>();// 線程容器
// 任務隊列容器,也可以用Queue<Runnable> 遵循 FIFO 規則
private final static LinkedList<Runnable> TASK_QUEUE = new LinkedList<>();
// 拒絕策略
private final static DiscardPolicy DEFAULT_DISCARD_POLICY = () -> {
throw new DiscardException("[拒絕執行] - [任務隊列溢出...]");
};
private int minSize;//最小線程
private int maxSize;//最大線程
private int activeSize;//活躍線程
SimpleThreadPoolExecutor() {
this(DEFAULT_MIN_THREAD_SIZE, DEFAULT_ACTIVE_THREAD_SIZE, DEFAULT_MAX_THREAD_SIZE, DEFAULT_WORKER_QUEUE_SIZE, DEFAULT_DISCARD_POLICY);
}
SimpleThreadPoolExecutor(int minSize, int activeSize, int maxSize, int queueSize, DiscardPolicy discardPolicy){
this.minSize = minSize;
this.activeSize = activeSize;
this.maxSize = maxSize;
this.queueSize = queueSize;
this.discardPolicy = discardPolicy;
initPool();
}
void submit(Runnable runnable) {
if (destroy) {
throw new IllegalStateException("線程池已銷燬...");
}
synchronized (TASK_QUEUE) {
if (TASK_QUEUE.size() > queueSize) {//如果當前任務隊超出隊列限制,後續任務拒絕執行
discardPolicy.discard();
}
// 1.將任務添加到隊列
TASK_QUEUE.addLast(runnable);
// 2.喚醒等待的線程去執行任務
TASK_QUEUE.notifyAll();
}
}
void shutdown() throws InterruptedException {
int activeCount = THREAD_GROUP.activeCount();
while (!TASK_QUEUE.isEmpty() && activeCount > 0) {
// 如果還有任務,那就休息一會
Thread.sleep(100);
}
int intVal = WORKER_TASKS.size();//如果線程池中沒有線程,那就不用關了
while (intVal > 0) {
for (WorkerTask task : WORKER_TASKS) {
//當任務隊列爲空的時候,線程狀態纔會爲 BLOCKED ,所以可以打斷掉,相反等任務執行完在關閉
if (task.taskState == TaskState.BLOCKED) {
task.close();
intVal--;
} else {
Thread.sleep(50);
}
}
}
this.destroy = true;
//資源回收
TASK_QUEUE.clear();
WORKER_TASKS.clear();
this.interrupt();
System.out.println("線程關閉");
}
private void createWorkerTask() {
WorkerTask task = new WorkerTask();
//剛創建出來的線程應該是未使用的
task.taskState = TaskState.FREE;
WORKER_TASKS.add(task);
task.start();
}
/**
* 初始化操作
*/
private void initPool() {
for (int i = 0; i < this.minSize; i++) {
this.createWorkerTask();
}
this.threadPoolSize = minSize;
this.start();//自己啓動自己
}
@Override
public void run() {
while (!destroy) {
try {
Thread.sleep(5_000L);
if (TASK_QUEUE.size() > activeSize && threadPoolSize < activeSize) { // 第一次擴容到 activeSize 大小
for (int i = threadPoolSize; i < activeSize; i++) {
createWorkerTask();
}
this.threadPoolSize = activeSize;
System.out.println("[初次擴充] - [" + toString() + "]");
} else if (TASK_QUEUE.size() > maxSize && threadPoolSize < maxSize) {// 第二次擴容到最大線程
System.out.println();
for (int i = threadPoolSize; i < maxSize; i++) {
createWorkerTask();
}
this.threadPoolSize = maxSize;
System.out.println("[再次擴充] - [" + toString() + "]");
} else {
//防止線程在submit的時候,其他線程獲取到鎖幹壞事
synchronized (WORKER_TASKS) {
int releaseSize = threadPoolSize - activeSize;
Iterator<WorkerTask> iterator = WORKER_TASKS.iterator();// List不允許在for中刪除集合元素,所以這裏需要使用迭代器
while (iterator.hasNext()) {
if (releaseSize <= 0) {
break;
}
WorkerTask task = iterator.next();
//不能回收正在運行的線程,只回收空閒線程
if (task.taskState == TaskState.FREE) {
task.close();
iterator.remove();
releaseSize--;
}
}
System.out.println("[資源回收] - [" + toString() + "]");
}
threadPoolSize = activeSize;
}
} catch (InterruptedException e) {
System.out.println("資源釋放");
}
}
}
@Override
public String toString() {
return "SimpleThreadPoolExecutor{" +
"threadPoolSize=" + threadPoolSize +
", taskQueueSize=" + TASK_QUEUE.size() +
", minSize=" + minSize +
", maxSize=" + maxSize +
", activeSize=" + activeSize +
'}';
}
}
測試一把
創建一個測試類
public class SimpleExecutorTest {
public static void main(String[] args) throws InterruptedException {
SimpleThreadPoolExecutor executor = new SimpleThreadPoolExecutor();
IntStream.range(0, 30).forEach(i ->
executor.submit(() -> {
System.out.printf("[線程] - [%s] 開始工作...\n", Thread.currentThread().getName());
try {
Thread.sleep(2_000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.printf("[線程] - [%s] 工作完畢...\n", Thread.currentThread().getName());
})
);
//executor.shutdown();如果放開註釋即會執行完所有任務關閉線程池
}
}
日誌分析: 從日誌中可以看到,初始化的時候是2
個線程在工作,執行速度較爲緩慢,當經過第一次擴容後,會觀察到線程池裏線程個數增加了,執行任務的速度就越來越快了,本文一共擴容了2次,第一次是擴容到activeSize
的大小,第二次是擴容到maxSize
,在執行任務的過程中,當線程數過多的時候就會觸發回收機制…
[線程] - [MY-THREAD-NAME-1] 開始工作...
[線程] - [MY-THREAD-NAME-2] 開始工作...
[線程] - [MY-THREAD-NAME-1] 工作完畢...
[線程] - [MY-THREAD-NAME-1] 開始工作...
[線程] - [MY-THREAD-NAME-2] 工作完畢...
[線程] - [MY-THREAD-NAME-2] 開始工作...
[線程] - [MY-THREAD-NAME-1] 工作完畢...
[線程] - [MY-THREAD-NAME-1] 開始工作...
[線程] - [MY-THREAD-NAME-2] 工作完畢...
[線程] - [MY-THREAD-NAME-2] 開始工作...
[初次擴充] - [SimpleThreadPoolExecutor{threadPoolSize=5, taskQueueSize=44, minSize=2, maxSize=10, activeSize=5}]
[線程] - [MY-THREAD-NAME-3] 開始工作...
...
[線程] - [MY-THREAD-NAME-6] 開始工作...
[線程] - [MY-THREAD-NAME-7] 開始工作...
[再次擴充] - [SimpleThreadPoolExecutor{threadPoolSize=10, taskQueueSize=30, minSize=2, maxSize=10, activeSize=5}]
[線程] - [MY-THREAD-NAME-10] 開始工作...
...
[線程] - [MY-THREAD-NAME-5] 開始工作...
[資源回收] - [SimpleThreadPoolExecutor{threadPoolSize=10, taskQueueSize=4, minSize=2, maxSize=10, activeSize=5}]
[線程] - [MY-THREAD-NAME-4] 工作完畢...
...
[線程] - [MY-THREAD-NAME-7] 工作完畢...
[資源回收] - [SimpleThreadPoolExecutor{threadPoolSize=5, taskQueueSize=0, minSize=2, maxSize=10, activeSize=5}]
[資源回收] - [SimpleThreadPoolExecutor{threadPoolSize=5, taskQueueSize=0, minSize=2, maxSize=10, activeSize=5}]
總結
通過本文,大致可以瞭解線程池的工作原理和實現方式,學習的過程中,就是要知其然知其所以然。這樣才能更好地駕馭它,更好地去理解和使用,也能更好地幫助我們觸類旁通
,後面的文章中會詳細介紹java.util.concurrent
中的線程池
。
- 說點什麼
- 個人QQ:1837307557
- battcn開源羣(適合新手):391619659
微信公衆號:battcn
(歡迎調戲)