併發編程(十六):線程池

一,ThreadPoolExecutor 概述

1,線程池優勢

    在Java中,如果每個請求到達就創建一個線程,創建線程和銷燬線程對系統資源的消耗都非常大,甚至可能比實際業務處理消耗的資源都大。同時,如果在JVM中創建太多的線程,也可能由於過度消耗內存或調度切換從而導致系統資源不足。

    爲了解決上面提出的問題,就有了線程池的概念。線程池,就是在一個線程容器中提前放置一定量的初始化線程,如果業務需要創建線程進行業務處理,則直接從線程池中獲取一個線程進行執行,執行完成後歸還線程到線程池,同時線程池也會對創建線程進行限制,不會無休止的創建下去。

    使用線程池後,可以後下面幾點優勢:

        * 減低創建線程和銷燬線程的開銷

        * 提高響應速度,當有新任務需要執行時不需要等待線程創建時就可以直接執行(如果有空閒線程)

        * 合理的設置線程池的大小可以避免因爲硬件瓶頸帶來的性能問題

2,類圖

3,線程池常用API

// 線程池初始化
public ThreadPoolExecutor(
	// 核心線程數
	int corePoolSize,
	// 最大線程數
	int maximumPoolSize,
	// 線程空閒保留時間
	long keepAliveTime,
	// 線程保留單位
	TimeUnit unit,
	// 線程任務阻塞容器
	BlockingQueue<Runnable> workQueue,
	// 線程工廠,一般取默認
	ThreadFactory threadFactory,
	// 拒絕策略
	RejectedExecutionHandler handler);
	
// 線程執行,不帶返回值
public void execute(Runnable command);

// 線程執行,帶返回值
public <T> Future<T> submit(Callable<T> task);
public <T> Future<T> submit(Runnable task, T result);
public Future<?> submit(Runnable task);

// 關閉線程池
public void shutdown();

4,線程池常量解析,分爲線程池常量和Future常量兩部分

/********************* 線程池常量 **********************/
// 數量爲,該值是29
// 線程池把 Integer 的32位拆分爲高3位和低29位,
// 通過高三位表示狀態,低29位表示正在執行的線程數量,
private static final int COUNT_BITS = Integer.SIZE - 3;

// 最大允許執行的線程數量,因爲高三位表示狀態,所以天然限制爲29位
private static final int CAPACITY   = (1 << COUNT_BITS) - 1;

// 線程池運行狀態
// -1 的二進制是 11111111 11111111 11111111 11111111
// 右移29位就是  11100000 00000000 00000000 00000000
// 高三位表示狀態,所以 RUNNING 的狀態就是 111
private static final int RUNNING    = -1 << COUNT_BITS;

// 線程池關閉狀態,不接收新任務,但是執行隊列中的人物
// 狀態爲 0
private static final int SHUTDOWN   =  0 << COUNT_BITS;

// 線程池停止狀態,不接受新任務,不執行隊列人物,終止執行任務
// 狀態爲 1
private static final int STOP       =  1 << COUNT_BITS;

// 所有任務都已經結束,線程數量爲0,處於該狀態的線程池即將調用 terminated()方法
// 狀態爲 10
private static final int TIDYING    =  2 << COUNT_BITS;

// terminated()方法執行完成
// 狀態爲 11
private static final int TERMINATED =  3 << COUNT_BITS;

/********************* Future常量 **********************/
// Future 常量主要是對 state 狀態的幾種情況分析
// NEW 新建狀態,表示這個 FutureTask還沒有開始運行
private static final int NEW = 0; 

// COMPLETING 完成狀態,表示 FutureTask 任務已經計算完畢了
// 但是還有一些後續操作,例如喚醒等待線程操作,還沒有完成。
private static final int COMPLETING = 1;

// FutureTask 任務完結,正常完成,沒有發生異常
private static final int NORMAL = 2;

// FutureTask 任務完結,因爲發生異常。
private static final int EXCEPTIONAL = 3;

// FutureTask 任務完結,因爲取消任務
private static final int CANCELLED = 4;

// FutureTask 任務完結,也是取消任務,不過發起了中斷運行任務線程的中斷請求
private static final int INTERRUPTING = 5;

// FutureTask 任務完結,也是取消任務,已經完成了中斷運行任務線程的中斷請求
private static final int INTERRUPTED = 6;

5,JDK提供的幾種常用線程池,阿里開發手冊不提倡用內置的線程池,建議通過構造器自行初始化,後續自行構造的執行流程會分析,該部分參考即可;跟源碼可以發下,各種初始化方式最終也是通過構造器初始化!

// 初始化定長線程池
// 內置指定的核心線程數和最大線程數,並按照線程池流程執行
public static ExecutorService newFixedThreadPool(int nThreads);

// 初始化緩存的線程池
// 沒有核心線程數,默認最大線程數爲 Integer.MAX_VALUE,阻塞隊列爲 SynchronousQueue,不保存數據
// 所有對於緩存的線程池來說,接收一個任務的同時就需要執行一個任務
public static ExecutorService newCachedThreadPool();

// 初始化單例的線程池
// 一次最多執行一個線程任務,多線程競爭時,添加到阻塞隊列等候
public static ExecutorService newSingleThreadExecutor();

// 初始化定時的線程池
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize);

6,功能DEMO

package com.gupao.concurrent;

import java.util.concurrent.*;

/**
 * @author pj_zhang
 * @create 2019-10-31 21:56
 **/
public class ThreadPoolTest {

    private static ThreadPoolExecutor executor =
            new ThreadPoolExecutor(20, 20,
                    0L, TimeUnit.SECONDS, new LinkedBlockingQueue<>());

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        Thread thread = new Thread(() -> {
            System.out.println("THREAD 線程執行");
        });

        Runnable runnable = () -> {
            System.out.println("RUNNABLE 線程執行");
        };

        Callable<String> callable = () -> {
            Thread.sleep(2000);
            return "Callable 線程執行";
        };

        executor.execute(thread);
        executor.execute(runnable);
        // 此處打印時間,是爲了演示 Future 的阻塞獲取結果
        System.out.println("callable執行前:" + System.currentTimeMillis());
        Future<String> future = executor.submit(callable);
        String callableResult = future.get();
        System.out.println("callable執行後:" + System.currentTimeMillis() + ", " + callableResult);
    }

}

二,源碼分析

1,底層方法分析

1.1,ctlOf(int rs, int wc):獲取線程池狀態+數量的 Integer 值,

private static int ctlOf(int rs, int wc) { 
	return rs | wc; 
}

1.2,workerCountOf(int c):獲取工作線程數量

private static int workerCountOf(int c)  {
	// 根據 ctlOf 獲取到的值,用低29位進行與運算,獲取到線程數量
	return c & CAPACITY; 
}

1.3,runStateOf(int c):獲取線程狀態

private static int runStateOf(int c)     { 
    return c & ~CAPACITY; 
}

2,ThreadPoolExecutor 初始化及執行流程

2.1,ThreadPoolExecutor()

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;
}

2.2,線程池執行流程

    * 接收到線程任務後,首先判斷核心線程有沒有被全部佔用;沒有被全部佔用,隨機構建一個線程執行任務

    * 如果核心線程全部被佔用,查看阻塞隊列是否已滿;如果沒有滿,添加到阻塞隊列

    * 如果阻塞隊列已滿,繼續看最大線程數有沒有被全部佔用;如果存在空閒,構建線程執行任務

    * 如果最大線程數已經全部佔用,根據定義的拒絕策略進行拒絕操作

2.3,線程池拒絕策略

    * 線程池提供了四種拒絕策略,分別是 RejectedExecutionHandler 接口的四種實現

// java.util.concurrent.ThreadPoolExecutor.AbortPolicy
// 異常處理
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
	throw new RejectedExecutionException("Task " + r.toString() +
										 " rejected from " +
										 e.toString());
}

// java.util.concurrent.ThreadPoolExecutor.DiscardOldestPolicy
// 丟棄最前面的任務,重新添加執行
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
	if (!e.isShutdown()) {
		e.getQueue().poll();
		e.execute(r);
	}
}

// java.util.concurrent.ThreadPoolExecutor.DiscardPolicy
// 什麼都不做,即丟棄當前任務
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
}

// java.util.concurrent.ThreadPoolExecutor.CallerRunsPolicy
// 只要線程池沒有關閉,則直接開線程運行,該策略建議慎用,不收控制
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
	if (!e.isShutdown()) {
		r.run();
	}
}

// 最後,業務可以自定義拒絕方式,只需要實現 RejectedExecutionHandler 接口,然後重寫其接口方法

3,execute()

    * execute(Runnable command):剛纔對線程池的大概執行流程進行了分析,該方法內可以看出代碼實現

public void execute(Runnable command) {
	if (command == null)
		throw new NullPointerException();
	// 獲取狀態位 + 數量位的 int 對象
	int c = ctl.get();
	// 獲取工作線程數量,首先判斷核心線程數
	if (workerCountOf(c) < corePoolSize) {
		// 存在空閒的核心線程,進行線程執行
		if (addWorker(command, true))
			return;
		// 如果執行失敗,重新對 c 賦值,說明存在線程競爭
		c = ctl.get();
	}
	// isRunning(c):線程池依舊運行狀態
	// workQueue.offer(command):添加到隊列成功
	// 次數是判斷加入到阻塞隊列是否成功
	if (isRunning(c) && workQueue.offer(command)) {
		int recheck = ctl.get();
		// 如果線程池不處於運行狀態,則從隊列中移除當前任務
		// 並執行拒絕策略
		if (! isRunning(recheck) && remove(command))
			reject(command);
		// 如果之前的線程已經被銷燬完,新建一個線程
		else if (workerCountOf(recheck) == 0)
			addWorker(null, false);
	}
	// 阻塞隊列添加失敗,喚醒最大線程執行,
	else if (!addWorker(command, false))
		// 最大線程執行失敗,進行拒絕策略處理
		reject(command);
}

    * addWorker(Runnable firstTask, boolean core)

// Runnable firstTask:當前線程任務
// boolean core:是否核心線程
private boolean addWorker(Runnable firstTask, boolean core) {
	retry:
	for (;;) {
		// 獲取線程執行狀態
		int c = ctl.get();
		// 這部分計算後續搞明白再填充 TODO
		int rs = runStateOf(c);
		// 線程池已經關閉,不再接受新任務
		// SHUTDOWN 狀態不接受新任務,但仍然會執行已經加入任務隊列的任務
		// 所以當進入 SHUTDOWN 狀態,而傳進來的任務爲空,並且任務隊列不爲空的時候,是允許添加新線程的,
		// 如果把這個條件取反,就表示不允許添加 worker
		if (rs >= SHUTDOWN &&
			! (rs == SHUTDOWN &&
			   firstTask == null &&
			   ! workQueue.isEmpty()))
			return false;
		// 自旋添加任務
		for (;;) {
			// 獲取工作線程
			int wc = workerCountOf(c);
			// 首先判斷是否大於最大允許數量
			// 然後根據是否核心線程判斷是否大於核心線程數或者最大線程數
			if (wc >= CAPACITY ||
				wc >= (core ? corePoolSize : maximumPoolSize))
				return false;
			// 對c遞增,也就是工作線程數遞增
			if (compareAndIncrementWorkerCount(c))
				break retry;
			// 遞增失敗,說明存在線程競爭或者狀態變更,繼續自旋處理
			c = ctl.get(); 
			// 此處不等於,說明存在線程池狀態變更
			// 等於,說明只是存在線程競爭造成的CAS失敗
			if (runStateOf(c) != rs)
				continue retry;
		}
	}

	// 上半部分基本是對線程池狀態及工作線程數進行判斷,並最終對工作線程+1,表示當前線程已經搶佔到一個線程位置
	boolean workerStarted = false;
	boolean workerAdded = false;
	Worker w = null;
	try {
		// 包裝線程對象爲 Worker 對象
		w = new Worker(firstTask);
		// 通過 ThreadFactory 構建一個新的線程
		final Thread t = w.thread;
		if (t != null) {
			// 此處加重入鎖
			final ReentrantLock mainLock = this.mainLock;
			mainLock.lock();
			try {
				// 繼續獲取線程池狀態進行判斷
				int rs = runStateOf(ctl.get());

				if (rs < SHUTDOWN ||
					(rs == SHUTDOWN && firstTask == null)) {
					// 如果線程已經運行中,則說明存在問題,此處線程還沒有啓用
					if (t.isAlive())
						throw new IllegalThreadStateException();
					// 添加線程包裝後的Worker對象到列表中
					workers.add(w);
					int s = workers.size();
					if (s > largestPoolSize)
						largestPoolSize = s;
					// 表示工作線程創建成功
					workerAdded = true;
				}
			} finally {
				mainLock.unlock();
			}
			if (workerAdded) {
				// 線程啓動,
				// 此處注意,Worker 實現了 Runnable接口,則此處是調用 Worker.run()
				t.start();
				workerStarted = true;
			}
		}
	} finally {
		// 如果添加失敗,則遞減工作線程數
		if (! workerStarted)
			addWorkerFailed(w);
	}
	return workerStarted;
}

    * runWorker(Worker w):Worker的 run() 方法內部調用該方法

final void runWorker(Worker w) {
	Thread wt = Thread.currentThread();
	// 獲取初始化 Worker時傳遞的線程
	// 此處分析的是 execute() 觸發,如果是 submit() 觸發,此處的Runnable實現類應該爲 FutureTask,
	// task.run() 最終調用 FutureTask.run()方法,此處會對 Future的狀態進行處理,實現阻塞獲取的功能
	Runnable task = w.firstTask;
	w.firstTask = null;
	// unlock,表示當前 worker 線程允許中斷,因爲 new Worker 默認的 state=-1,
	// 此處是調用Worker 類的 tryRelease()方法,將 state 置爲 0, 
	// 而 interruptIfStarted()中只有 state>=0 才允許調用中斷
	w.unlock();
	boolean completedAbruptly = true;
	try {
		// 任務不爲空,則持續執行
		// getTask():此處表示不斷從阻塞隊列中獲取元素
		while (task != null || (task = getTask()) != null) {
			w.lock();
			// 線程池狀態爲stop時不接受新任務,並中斷正在執行的人物
			// (Thread.interrupted() &&runStateAtLeast(ctl.get(), STOP)確保線程中斷標誌位爲 true 且是 stop 狀態以上,接着清除了中斷標誌
			// !wt.isInterrupted()則再一次檢查保證線程需要設置中斷標誌位
			if ((runStateAtLeast(ctl.get(), STOP) ||
				 (Thread.interrupted() &&
				  runStateAtLeast(ctl.get(), STOP))) &&
				!wt.isInterrupted())
				wt.interrupt();
			try {
				// 默認沒有實現功能,前置處理
				beforeExecute(wt, task);
				Throwable thrown = null;
				try {
					// 此處注意 task.run() 的task的不同實現
					// 在submit()觸發的功能中,表示FutureTask
					task.run();
				} catch (RuntimeException x) {
					thrown = x; throw x;
				} catch (Error x) {
					thrown = x; throw x;
				} catch (Throwable x) {
					thrown = x; throw new Error(x);
				} finally {
					// 後置執行,沒有處理
					afterExecute(task, thrown);
				}
			} finally {
				task = null;
				w.completedTasks++;
				w.unlock();
			}
		}
		completedAbruptly = false;
	} finally {
		// 將入參 worker 從數組 workers 裏刪除掉;
		// 根據布爾值 allowCoreThreadTimeOut 來決定是否補充新的 Worker 進數組workers
		processWorkerExit(w, completedAbruptly);
	}
}

    * getTask():從阻塞隊列中獲取下一個有效任務。線程池定義的超時處理再該部分實現

private Runnable getTask() {
	boolean timedOut = false;

	for (;;) {
		int c = ctl.get();
		int rs = runStateOf(c);

		// 校驗線程池狀態
		if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
			decrementWorkerCount();
			return null;
		}

		// 獲取工作線程
		int wc = workerCountOf(c);
		// 對超時線程進行時間控制
		// allowCoreThreadTimeOut默認爲false,表示核心線程不收控制
		// wc > corePoolSize:超過核心線程,即最大線程數,則觸發控制
		boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;

		// timedOut爲true,說明上次阻塞操作已經超時,則工作線程數-1,並返回null
		if ((wc > maximumPoolSize || (timed && timedOut))
			&& (wc > 1 || workQueue.isEmpty())) {
			if (compareAndDecrementWorkerCount(c))
				return null;
			continue;
		}

		try {
			// 此處就是對超時控制的處理,在從隊列中獲取數據時,阻塞獲取
			Runnable r = timed ?
				workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
				workQueue.take();
			// 如果拿到任務,則直接返回去進行處理
			if (r != null)
				return r;
			// 走到這一步,說明超時,在下一步時候進行回收處理
			timedOut = true;
		} catch (InterruptedException retry) {
			timedOut = false;
		}
	}
}

    * addWorkerFailed(Worker w):添加工作線程失敗

private void addWorkerFailed(Worker w) {
	final ReentrantLock mainLock = this.mainLock;
	mainLock.lock();
	try {
		// 從列表中移除當前 Worker
		if (w != null)
			workers.remove(w);
		// 工作線程數遞減
		decrementWorkerCount();
		// 嘗試修改線程狀態爲 Terminate
		tryTerminate();
	} finally {
		mainLock.unlock();
	}
}

private void decrementWorkerCount() {
	do {} while (! compareAndDecrementWorkerCount(ctl.get()));
}

    * reject(Runnable command):拒絕策略

final void reject(Runnable command) {
	// 直接執行拒絕策略
	handler.rejectedExecution(command, this);
}

4,submit():同時對 Future 進行分析

    * submit(Callable<T> task):觸發線程執行

public <T> Future<T> submit(Callable<T> task) {
	if (task == null) throw new NullPointerException();
	// 初始化化一個 FutureTask,實現了 Runnable 接口
	RunnableFuture<T> ftask = newTaskFor(task);
	// 直接執行 execute 方法
	execute(ftask);
	return ftask;
}

protected <T> RunnableFuture<T> newTaskFor(Callable<T> callable) {
	return new FutureTask<T>(callable);
}

    * 在 runWorker() 方法中,觸發 task.run(),實際調用的是 Future.run()方法

public void run() {
	// 狀態不爲new,直接執行異常
	if (state != NEW ||
		!UNSAFE.compareAndSwapObject(this, runnerOffset,
									 null, Thread.currentThread()))
		return;
	try {
		Callable<V> c = callable;
		// 線程一切正常,準備執行
		if (c != null && state == NEW) {
			V result;
			boolean ran;
			try {
				// 直接調用Callable的call方法,並返回結果
				result = c.call();
				ran = true;
			} catch (Throwable ex) {
				result = null;
				ran = false;
				setException(ex);
			}
			// 設置結果
			if (ran)
				set(result);
		}
	} finally {
		runner = null;
		int s = state;
		if (s >= INTERRUPTING)
			handlePossibleCancellationInterrupt(s);
	}
}

    * set(V v):設置執行結果

protected void set(V v) {
	// 設置狀態爲完成
	if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
		outcome = v;
		// 設置正常結束
		UNSAFE.putOrderedInt(this, stateOffset, NORMAL);
		// 完成後續處理,喚醒等待節點
		finishCompletion();
	}
}

    * finishCompletion():該方法主要是喚醒等待節點,去返回結果中拿數據

private void finishCompletion() {
	// 獲取等待節點
	for (WaitNode q; (q = waiters) != null;) {
		if (UNSAFE.compareAndSwapObject(this, waitersOffset, q, null)) {
			for (;;) {
				// 等待節點線程優先,直接喚醒
				Thread t = q.thread;
				if (t != null) {
					q.thread = null;
					LockSupport.unpark(t);
				}
				// 遞歸下一個等待節點同步處理
				WaitNode next = q.next;
				if (next == null)
					break;
				q.next = null; // unlink to help gc
				q = next;
			}
			break;
		}
	}

	done();

	callable = null;        // to reduce footprint
}

5,Funture.get()

    * get():阻塞獲取數據

public V get() throws InterruptedException, ExecutionException {
	// 獲取Future對應的線程執行狀態,如果沒有執行完直接等待
	int s = state;
	if (s <= COMPLETING)
		s = awaitDone(false, 0L);
	// 執行完成後,解析結果集
	return report(s);
}

    * awaitDone(boolean timed, long nanos)

private int awaitDone(boolean timed, long nanos) throws InterruptedException {
	final long deadline = timed ? System.nanoTime() + nanos : 0L;
	WaitNode q = null;
	boolean queued = false;
	for (;;) {
		// 線程中斷,直接移除
		if (Thread.interrupted()) {
			removeWaiter(q);
			throw new InterruptedException();
		}

		int s = state;
		// 此處說明執行完成
		if (s > COMPLETING) {
			if (q != null)
				q.thread = null;
			return s;
		}
		// 還有後續操作沒有執行完成,暫時讓出時間片段,稍後執行
		else if (s == COMPLETING)
			Thread.yield();
		// 表示狀態爲null,構建等待節點,準備等待
		else if (q == null)
			q = new WaitNode();
		// 使用CAS添加等待節點到等待隊列
		else if (!queued)
			queued = UNSAFE.compareAndSwapObject(this, waitersOffset,
												 q.next = waiters, q);
		// 此處表示設置了超時
		else if (timed) {
			// 如果超時時間沒有獲取到值,則直接退出
			nanos = deadline - System.nanoTime();
			if (nanos <= 0L) {
				removeWaiter(q);
				return state;
			}
			LockSupport.parkNanos(this, nanos);
		}
		else
			// 添加等待隊列成功後,線程掛起等待,等待執行完成後進行喚醒,完成那部分已經分析
			LockSupport.park(this);
	}
}

    * report(int s):獲取返回值

private V report(int s) throws ExecutionException {
	// 獲取返回值
	Object x = outcome;
	if (s == NORMAL)
		return (V)x;
	if (s >= CANCELLED)
		throw new CancellationException();
	throw new ExecutionException((Throwable)x);
}

 

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