【高併發】通過原始碼深度解析ThreadPoolExecutor類是如何保證執行緒池正確執行的

冰河團隊發表於2022-05-10

大家好,我是冰河~~

對於執行緒池的核心類ThreadPoolExecutor來說,有哪些重要的屬性和內部類為執行緒池的正確執行提供重要的保障呢?

ThreadPoolExecutor類中的重要屬性

在ThreadPoolExecutor類中,存在幾個非常重要的屬性和方法,接下來,我們就介紹下這些重要的屬性和方法。

ctl相關的屬性

AtomicInteger型別的常量ctl是貫穿執行緒池整個生命週期的重要屬性,它是一個原子類物件,主要用來儲存執行緒的數量和執行緒池的狀態,我們看下與這個屬性相關的程式碼如下所示。

//主要用來儲存執行緒數量和執行緒池的狀態,高3位儲存執行緒狀態,低29位儲存執行緒數量
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
//執行緒池中執行緒的數量的位數(32-3)
private static final int COUNT_BITS = Integer.SIZE - 3;
//表示執行緒池中的最大執行緒數量
//將數字1的二進位制值向右移29位,再減去1
private static final int CAPACITY   = (1 << COUNT_BITS) - 1;
//執行緒池的執行狀態
private static final int RUNNING    = -1 << COUNT_BITS;
private static final int SHUTDOWN   =  0 << COUNT_BITS;
private static final int STOP       =  1 << COUNT_BITS;
private static final int TIDYING    =  2 << COUNT_BITS;
private static final int TERMINATED =  3 << COUNT_BITS;
//獲取執行緒狀態
private static int runStateOf(int c)     { return c & ~CAPACITY; }
//獲取執行緒數量
private static int workerCountOf(int c)  { return c & CAPACITY; }
private static int ctlOf(int rs, int wc) { return rs | wc; }
private static boolean runStateLessThan(int c, int s) {
	return c < s;
}
private static boolean runStateAtLeast(int c, int s) {
	return c >= s;
}
private static boolean isRunning(int c) {
	return c < SHUTDOWN;
}
private boolean compareAndIncrementWorkerCount(int expect) {
	return ctl.compareAndSet(expect, expect + 1);
}
private boolean compareAndDecrementWorkerCount(int expect) {
	return ctl.compareAndSet(expect, expect - 1);
}
private void decrementWorkerCount() {
	do {} while (! compareAndDecrementWorkerCount(ctl.get()));
}

對於執行緒池的各狀態說明如下所示。

  • RUNNING:執行狀態,能接收新提交的任務,並且也能處理阻塞佇列中的任務
  • SHUTDOWN: 關閉狀態,不能再接收新提交的任務,但是可以處理阻塞佇列中已經儲存的任務,當執行緒池處於RUNNING狀態時,呼叫shutdown()方法會使執行緒池進入該狀態
  • STOP: 不能接收新任務,也不能處理阻塞佇列中已經儲存的任務,會中斷正在處理任務的執行緒,如果執行緒池處於RUNNING或SHUTDOWN狀態,呼叫shutdownNow()方法,會使執行緒池進入該狀態
  • TIDYING: 如果所有的任務都已經終止,有效執行緒數為0(阻塞佇列為空,執行緒池中的工作執行緒數量為0),執行緒池就會進入該狀態。
  • TERMINATED: 處於TIDYING狀態的執行緒池呼叫terminated ()方法,會使用執行緒池進入該狀態

也可以按照ThreadPoolExecutor類的註釋,將執行緒池的各狀態之間的轉化總結成如下圖所示。

  • RUNNING -> SHUTDOWN:顯式呼叫shutdown()方法, 或者隱式呼叫了finalize()方法
  • (RUNNING or SHUTDOWN) -> STOP:顯式呼叫shutdownNow()方法
  • SHUTDOWN -> TIDYING:當執行緒池和任務佇列都為空的時候
  • STOP -> TIDYING:當執行緒池為空的時候
  • TIDYING -> TERMINATED:當 terminated() hook 方法執行完成時候

其他重要屬性

除了ctl相關的屬性外,ThreadPoolExecutor類中其他一些重要的屬性如下所示。

//用於存放任務的阻塞佇列  
private final BlockingQueue<Runnable> workQueue;
//可重入鎖
private final ReentrantLock mainLock = new ReentrantLock();
//存放執行緒池中執行緒的集合,訪問這個集合時,必須獲得mainLock鎖
private final HashSet<Worker> workers = new HashSet<Worker>();
//在鎖內部阻塞等待條件完成
private final Condition termination = mainLock.newCondition();
//執行緒工廠,以此來建立新執行緒
private volatile ThreadFactory threadFactory;
//拒絕策略
private volatile RejectedExecutionHandler handler;
//預設的拒絕策略
private static final RejectedExecutionHandler defaultHandler = new AbortPolicy();

ThreadPoolExecutor類中的重要內部類

在ThreadPoolExecutor類中存在對於執行緒池的執行至關重要的內部類,Worker內部類和拒絕策略內部類。接下來,我們分別看這些內部類。

Worker內部類

Worker類從原始碼上來看,實現了Runnable介面,說明其本質上是一個用來執行任務的執行緒,接下來,我們看下Worker類的原始碼,如下所示。

private final class Worker extends AbstractQueuedSynchronizer implements Runnable{
	private static final long serialVersionUID = 6138294804551838833L;
	//真正執行任務的執行緒
	final Thread thread;
	//第一個Runnable任務,如果在建立執行緒時指定了需要執行的第一個任務
	//則第一個任務會存放在此變數中,此變數也可以為null
	//如果為null,則執行緒啟動後,通過getTask方法到BlockingQueue佇列中獲取任務
	Runnable firstTask;
	//用於存放此執行緒完全的任務數,注意:使用了volatile關鍵字
	volatile long completedTasks;
	
	//Worker類唯一的構造放大,傳遞的firstTask可以為null
	Worker(Runnable firstTask) {
		//防止在呼叫runWorker之前被中斷
		setState(-1);
		this.firstTask = firstTask;
		//使用ThreadFactory 來建立一個新的執行任務的執行緒
		this.thread = getThreadFactory().newThread(this);
	}
	//呼叫外部ThreadPoolExecutor類的runWorker方法執行任務
	public void run() {
		runWorker(this);
	}

	//是否獲取到鎖 
	//state=0表示鎖未被獲取
	//state=1表示鎖被獲取
	protected boolean isHeldExclusively() {
		return getState() != 0;
	}

	protected boolean tryAcquire(int unused) {
		if (compareAndSetState(0, 1)) {
			setExclusiveOwnerThread(Thread.currentThread());
			return true;
		}
		return false;
	}

	protected boolean tryRelease(int unused) {
		setExclusiveOwnerThread(null);
		setState(0);
		return true;
	}

	public void lock()        { acquire(1); }
	public boolean tryLock()  { return tryAcquire(1); }
	public void unlock()      { release(1); }
	public boolean isLocked() { return isHeldExclusively(); }

	void interruptIfStarted() {
		Thread t;
		if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) {
			try {
				t.interrupt();
			} catch (SecurityException ignore) {
			}
		}
	}
}

在Worker類的構造方法中,可以看出,首先將同步狀態state設定為-1,設定為-1是為了防止runWorker方法執行之前被中斷。這是因為如果其他執行緒呼叫執行緒池的shutdownNow()方法時,如果Worker類中的state狀態的值大於0,則會中斷執行緒,如果state狀態的值為-1,則不會中斷執行緒。

Worker類實現了Runnable介面,需要重寫run方法,而Worker的run方法本質上呼叫的是ThreadPoolExecutor類的runWorker方法,在runWorker方法中,會首先呼叫unlock方法,該方法會將state置為0,所以這個時候呼叫shutDownNow方法就會中斷當前執行緒,而這個時候已經進入了runWork方法,就不會在還沒有執行runWorker方法的時候就中斷執行緒。

注意:大家需要重點理解Worker類的實現。

拒絕策略內部類

線上程池中,如果workQueue阻塞佇列滿了,並且沒有空閒的執行緒池,此時,繼續提交任務,需要採取一種策略來處理這個任務。而執行緒池總共提供了四種策略,如下所示。

  • 直接丟擲異常,這也是預設的策略。實現類為AbortPolicy。
  • 用呼叫者所在的執行緒來執行任務。實現類為CallerRunsPolicy。
  • 丟棄佇列中最靠前的任務並執行當前任務。實現類為DiscardOldestPolicy。
  • 直接丟棄當前任務。實現類為DiscardPolicy。

在ThreadPoolExecutor類中提供了4個內部類來預設實現對應的策略,如下所示。

public static class CallerRunsPolicy implements RejectedExecutionHandler {

	public CallerRunsPolicy() { }

	public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
		if (!e.isShutdown()) {
			r.run();
		}
	}
}

public static class AbortPolicy implements RejectedExecutionHandler {

	public AbortPolicy() { }

	public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
		throw new RejectedExecutionException("Task " + r.toString() + " rejected from " + e.toString());
	}
}

public static class DiscardPolicy implements RejectedExecutionHandler {

	public DiscardPolicy() { }

	public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
	}
}

public static class DiscardOldestPolicy implements RejectedExecutionHandler {

	public DiscardOldestPolicy() { }


	public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
		if (!e.isShutdown()) {
			e.getQueue().poll();
			e.execute(r);
		}
	}
}

我們也可以通過實現RejectedExecutionHandler介面,並重寫RejectedExecutionHandler介面的rejectedExecution方法來自定義拒絕策略,在建立執行緒池時,呼叫ThreadPoolExecutor的構造方法,傳入我們自己寫的拒絕策略。

例如,自定義的拒絕策略如下所示。

public class CustomPolicy implements RejectedExecutionHandler {

	public CustomPolicy() { }

	public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
		if (!e.isShutdown()) {
			System.out.println("使用呼叫者所在的執行緒來執行任務")
			r.run();
		}
	}
}

使用自定義拒絕策略建立執行緒池。

new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                       60L, TimeUnit.SECONDS,
                       new SynchronousQueue<Runnable>(),
                       Executors.defaultThreadFactory(),
		       new CustomPolicy());

今天就到這兒吧,我是冰河,我們下期見~~

相關文章