Java線程池 -- ThreadPoolExecutor

摘要描述:【強制】線程池不允許使用 Executors 去創建,而是通過 ThreadPoolExecutor 的方式,這樣 的處理方式讓寫的同學更加明確線程池的運行規則,規避資源耗盡的風險。 說明:Executors 返回的線程池對象的弊端如下: 1)FixedThreadPool 和 SingleThreadPool: 允許的請求隊列長度爲 Integer.MAX_VALUE,可能會堆積大量的請求,從而導致 OOM。 2)CachedThreadPool 和 ScheduledThreadPool: 允許的創建線程數量爲 Integer.MAX_VALUE,可能會創建大量的線程,從而導致 OOM。

優勢: 
(1)降低資源消耗。通過重複利用已創建的線程降低線程創建、銷燬線程造成的消耗。 
(2)提高響應速度。當任務到達時,任務可以不需要等到線程創建就能立即執行。 
(3)提高線程的可管理性。線程是稀缺資源,如果無限制的創建,不僅會消耗系統資源,還會降低系統的穩定性,使用線程池可以進行統一的分配、調優和監控。

一、線程池的原理

1.1 線程池剛創建時,裏面沒有一個線程。任務隊列是作爲參數傳進來的。不過,就算隊列裏面有任務,線程池也不會馬上執行它們。

1.2. 當調用execute()方法添加一個任務時,任務通過 execute(Runnable)方法被添加到線程池,任務就是一個 Runnable類型的對象,任務的執行方法就是 Runnable類型對象的run()方法。 當一個任務通過execute(Runnable)方法欲添加到線程池時,線程池會做如下判斷:
a. 如果正在運行的線程數小於corePoolSize,即使線程池中的線程都處於空閒狀態,也會創建新的線程運行這個任務。
b. 如果正在運行的線程數大於或者等於corePoolSize,但是緩衝隊列 workQueue未滿,那麼任務將被放入緩存隊列。
c. 如果正在運行的線程數大於corePoolSize,緩衝隊列workQueue已滿,而且正在運行的線程數量小於maximumPoolSize,那麼還是要創建新的線程運行這個任務。
d. 如果正在運行的線程數大於corePoolSize,緩衝隊列workQueue已滿,而且正在運行的線程數量大於或等於maximumPoolSize,那麼線程池會拋出異常,告訴調用者“我不能再接受任務了”。

1.3 當一個線程完成任務時,它會從隊列中取下一個任務來執行。

1.4 當一個線程無事可做,超過一定的時間(keepAliveTime)時,線程池會判斷,如果當前運行的線程數大於corePoolSize時,那麼這個線程會被終止,線程池可以動態的調整池中的線程數,當線程池的所有任務完成後,它最終會收縮到corePoolSize的大小。

1.5 處理任務的優先級爲: 核心線程corePoolSize、任務隊列workQueue、最大線程maximumPoolSize,如果三者都滿了,使用handler處理被拒絕的任務。

二、線程池的使用場景
(1)單個任務處理的時間比較短; 
(2)需要處理的任務數量大;

三、線程池的配置參數

new ThreadPoolExecutor(int corePoolSize, int maximumPoolSize,long keepAliveTime, TimeUnit unit,BlockingQueue workQueue,RejectedExecutionHandler handler) 
(1)corePoolSize: 線程池維護線程的最少數量 (core : 核心) 
(2)maximumPoolSize: 線程池維護線程的最大數量 ,如果當線程池中的運行的線程數量到達這個數字時,新來的任務會拋出異常。
(3)keepAliveTime: 線程池維護線程所允許的空閒時間 ,表示線程沒有任務執行時最多能保持多少時間會停止,然後線程池維護的線程數目維持在corePoolSize。
(4)unit: 線程池維護線程所允許的空閒時間的單位 。
(5)workQueue: 線程池所使用的阻塞隊列,用來緩存等待執行的任務,如果當前對線程的需求超過了corePoolSize大小,纔會放在這裏。
(6)handler: 如果執行線程已滿,線程池對拒絕任務的處理策略。

線程池的阻塞隊列包含哪幾種選擇?

ArrayBlockingQueue:一個有邊界的阻塞隊列,它的內部實現是一個數組。它的容量在初始化時就確定不變。
LinkedBlockingQueue:阻塞隊列大小的配置是可選的,其內部實現是一個鏈表。
PriorityBlockingQueue:是一個沒有邊界的隊列,所有插入到PriorityBlockingQueue的對象必須實現java.lang.Comparable接口,隊列優先級的排序就是按照我們對這個接口的實現來定義的。
SynchronousQueue:隊列內部僅允許容納一個元素。當一個線程插入一個元素後會被阻塞,除非這個元素被另一個線程消費。

線程池對拒絕任務的處理策略有哪幾種?

ThreadPoolExecutor.AbortPolicy():拋出java.util.concurrent.RejectedExecutionException異常 。
ThreadPoolExecutor.CallerRunsPolicy():  重試添加當前的任務,他會自動重複調用execute()方法 。
ThreadPoolExecutor.DiscardOldestPolicy():  拋棄舊的任務 。
ThreadPoolExecutor.DiscardPolicy():  拋棄當前的任務。

四、線程池的使用

package com.layduo.web.test;

import java.io.Serializable;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
* @author layduo
* @createTime 2019年11月19日 上午10:42:39
* 
* @resource by https://www.cnblogs.com/sunhaoyu/articles/6955923.html
*/
public class MyThreadPoolExecutor {

	private static int produceTaskSleepTime = 1000;

	private static int consumeTaskSleepTime = 5000;

	private static int produceTaskMaxNumber = 10; // 定義最大添10個線程到線程池中

	private static int corePoolSize = 3;

	private static int maximumPoolSize = 5;

	private static long keepAliveTime = 3;

	public static void main(String[] args) throws InterruptedException {
		// 創建線程池
		ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime,
				TimeUnit.SECONDS, new ArrayBlockingQueue<>(3), new ThreadPoolExecutor.CallerRunsPolicy());

		for (int i = 1; i <= produceTaskMaxNumber; i++) {
			try {
				// 一個任務,並將其加入到線程池
				System.out.println("===================================================");
				String work = "task@ " + i;
				System.out.println("execute : " + work);

				threadPoolExecutor.execute(new ThreadPoolTask(work, threadPoolExecutor));
				// 便於觀察,等待一段時間
				Thread.sleep(produceTaskSleepTime);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		boolean flag = true;
		while (flag) {

			if (threadPoolExecutor.getQueue().size() <= 0 && threadPoolExecutor.getActiveCount() <= 0) {
				flag = false;
				System.out.println();
				System.out.println("====>線程池線程任務執行完畢");
				printlnThread(threadPoolExecutor);
				// 關閉線程池
				threadPoolExecutor.shutdown();
				// 檢測沒有關閉線程池,則強制關閉
				if (threadPoolExecutor.isTerminated()) {
					threadPoolExecutor.shutdownNow();
					System.out.println("========強制關閉線程池========");
				}
			}

			Thread.sleep(3000);
		}
	}

	// 線程池執行的任務
	public static class ThreadPoolTask implements Runnable, Serializable {

		private static final long serialVersionUID = 0;
		// 保存任務所需要的數據
		private Object threadPoolTaskData;
		private ThreadPoolExecutor threadPool;

		public ThreadPoolTask(Object works, ThreadPoolExecutor threadPoolExecutor) {
			this.threadPoolTaskData = works;
			this.threadPool = threadPoolExecutor;
		}

		@Override
		public void run() {

			printlnThread(threadPool);

			System.out.println();
			System.out.println("run ------> " + threadPoolTaskData + " by " + Thread.currentThread().getName());
			System.out.println("===================================================");
			try {
				// 便於觀察,等待一段時間
				Thread.sleep(consumeTaskSleepTime);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			threadPoolTaskData = null;
		}

		public Object getTask() {
			return this.threadPoolTaskData;
		}
	}

	public static void printlnThread(ThreadPoolExecutor threadPool) {
		System.out.println();
		int queueSize = threadPool.getQueue().size();
		System.out.println("====>當前排隊任務數:" + queueSize);

		int activeCount = threadPool.getActiveCount();
		System.out.println("====>當前活動任務數:" + activeCount);

		long completedTaskCount = threadPool.getCompletedTaskCount();
		System.out.println("====>執行完成任務數:" + completedTaskCount);

		long taskCount = threadPool.getTaskCount();
		System.out.println("====>線程池執行任務數:" + taskCount);

		// 通過這個數據可以知道線程池是否曾經滿過。如該數值等於線程池的最大大小,則表示線程池曾經滿過。
		int largest = threadPool.getLargestPoolSize();
		System.out.println("====>創建最大線程數:" + largest);

		System.out.println("====>線程池創建線程是否已滿:" + (largest == maximumPoolSize ? "是" : "否"));

		long poolSize = threadPool.getPoolSize();
		System.out.println("====>總線程數:" + poolSize);
	}

}

 運行輸出控制檯:

===================================================
execute : task@ 1

====>當前排隊任務數:0
====>當前活動任務數:1
====>執行完成任務數:0
====>線程池執行任務數:1
====>創建最大線程數:1
====>線程池創建線程是否已滿:否
====>總線程數:1

run ------> task@ 1 by pool-1-thread-1
===================================================
===================================================
execute : task@ 2

====>當前排隊任務數:0
====>當前活動任務數:2
====>執行完成任務數:0
====>線程池執行任務數:2
====>創建最大線程數:2
====>線程池創建線程是否已滿:否
====>總線程數:2

run ------> task@ 2 by pool-1-thread-2
===================================================
===================================================
execute : task@ 3

====>當前排隊任務數:0
====>當前活動任務數:3
====>執行完成任務數:0
====>線程池執行任務數:3
====>創建最大線程數:3
====>線程池創建線程是否已滿:否
====>總線程數:3

run ------> task@ 3 by pool-1-thread-3
===================================================
===================================================
execute : task@ 4
===================================================
execute : task@ 5

====>當前排隊任務數:1
====>當前活動任務數:3
====>執行完成任務數:1
====>線程池執行任務數:5
====>創建最大線程數:3
====>線程池創建線程是否已滿:否
====>總線程數:3

run ------> task@ 4 by pool-1-thread-1
===================================================
===================================================
execute : task@ 6

====>當前排隊任務數:1
====>當前活動任務數:3
====>執行完成任務數:2
====>線程池執行任務數:6
====>創建最大線程數:3
====>線程池創建線程是否已滿:否
====>總線程數:3

run ------> task@ 5 by pool-1-thread-2
===================================================
===================================================
execute : task@ 7

====>當前排隊任務數:1
====>當前活動任務數:3
====>執行完成任務數:3
====>線程池執行任務數:7
====>創建最大線程數:3
====>線程池創建線程是否已滿:否
====>總線程數:3

run ------> task@ 6 by pool-1-thread-3
===================================================
===================================================
execute : task@ 8
===================================================
execute : task@ 9
===================================================
execute : task@ 10

====>當前排隊任務數:3
====>當前活動任務數:4
====>執行完成任務數:3
====>線程池執行任務數:10
====>創建最大線程數:4
====>線程池創建線程是否已滿:否
====>總線程數:4

run ------> task@ 10 by pool-1-thread-4
===================================================

====>當前排隊任務數:2
====>當前活動任務數:4
====>執行完成任務數:4
====>線程池執行任務數:10
====>創建最大線程數:4
====>線程池創建線程是否已滿:否
====>總線程數:4

run ------> task@ 7 by pool-1-thread-1
===================================================

====>當前排隊任務數:1
====>當前活動任務數:4
====>執行完成任務數:5
====>線程池執行任務數:10
====>創建最大線程數:4
====>線程池創建線程是否已滿:否
====>總線程數:4

run ------> task@ 8 by pool-1-thread-2
===================================================

====>當前排隊任務數:0
====>當前活動任務數:4
====>執行完成任務數:6
====>線程池執行任務數:10
====>創建最大線程數:4
====>線程池創建線程是否已滿:否
====>總線程數:4

run ------> task@ 9 by pool-1-thread-3
===================================================

====>線程池線程任務執行完畢

====>當前排隊任務數:0
====>當前活動任務數:0
====>執行完成任務數:10
====>線程池執行任務數:10
====>創建最大線程數:4
====>線程池創建線程是否已滿:否
====>總線程數:3

線程池源碼解析

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章