java多線程之線程池ThreadPoolExecutor源碼分析

前言

上一篇咱們分析了線程池的架構和它的工作流程,並且最後創建了一個線程池,本篇咱們就來深入分析線程池的實現類ThreadPoolExecutor。

1、構造方法

構造方法中有4個方法,本質上都是調用的下面這個構造方法:

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、飽和策略

上面的構造方法中,我們着重需要注意的是飽和策略,線程池中定義了四種飽和策略:
1、CallerRunsPolicy

public static class CallerRunsPolicy implements RejectedExecutionHandler {
	public CallerRunsPolicy() { }
	//使用主線程執行新任務
	public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
		if (!e.isShutdown()) {
			//此方法相同於同步方法
			r.run();
		}
	}
}

2、 AbortPolicy(線程池默認的策略)

public static class AbortPolicy implements RejectedExecutionHandler {	
	public AbortPolicy() { }

	public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
		//拋出 RejectedExecutionException來拒絕新任務的處理
		throw new RejectedExecutionException("Task " + r.toString() + " rejected from " + e.toString());
	}
}

3、DiscardPolicy

public static class DiscardPolicy implements RejectedExecutionHandler {
	public DiscardPolicy() { }
	//不執行任何操作,丟棄新任務
	public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
	}
}

4、DiscardOldestPolicy

public static class DiscardOldestPolicy implements RejectedExecutionHandler {
	public DiscardOldestPolicy() { }
	//此策略將丟棄最早的未處理的任務
	public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
		if (!e.isShutdown()) {
			e.getQueue().poll();
			e.execute(r);
		}
	}
}

3、阻塞隊列

上一篇例子中,咱們使用Executors工具類創建了一個線程固定大小的線程池,方法內部是使用ThreadPoolExecutor創建的線程池,咱們看下源碼:

public static ExecutorService newFixedThreadPool(int nThreads) {
	return new ThreadPoolExecutor(nThreads, nThreads,0L, TimeUnit.MILLISECONDS,
								  new LinkedBlockingQueue<Runnable>());
}

使用的是LinkedBlockingQueue作爲阻塞隊列,LinkedBlockingQueue的默認構造函數允許的隊列長度是Integer.MAX_VALUE,若堆積大量的請求,可能會造成OOM,此處就是爲什麼《阿里巴巴 Java 開發手冊》中不推薦使用Executors工具類創建線程池的原因,要求使用 ThreadPoolExecutor 構造函數的方式,讓寫的同學更加明確線程池的運行規則,規避資源耗盡的風險。

4、execute方法

下面是執行流程圖:
在這裏插入圖片描述
對照流程圖,我們再來看源碼:

//ctl中存放的是int值,int值得高低位保存了線程池運行的狀態和有效線程的數量
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
private static int workerCountOf(int c) {
	return c & CAPACITY;
}
//任務隊列
private final BlockingQueue<Runnable> workQueue;
public void execute(Runnable command) {
	//如果任務爲null,則拋出異常
	if (command == null)
		throw new NullPointerException();
	//獲取線程池狀態和有效線程數
	int c = ctl.get();
	//以下有3步:
	//步驟1:
	//如果線程池工作的線程小於核心線程數
	if (workerCountOf(c) < corePoolSize) { 
		//則增加一個線程,並把該任務交給它去執行
		if (addWorker(command, true))
			//成功則返回
			return;
		//這裏說明創建核心線程失敗,需要再次獲取臨時變量c
		c = ctl.get();
	}
	//步驟2:
	// 走到這裏說明創建新的核心線程失敗,也就是當前工作線程數大於等於corePoolSize
	// 線程池的運行狀態是RUNNING,並且嘗試將新任務加入到阻塞隊列,成功返回true
	if (isRunning(c) && workQueue.offer(command)) {
		//進入到這裏,是已經向任務隊列投放任務成功
		//再次獲取線程池狀態和有效線程數
		int recheck = ctl.get();
		//如果線程池狀態不是RUNNING(線程池異常終止了),將線程從工作隊列中移除
		if (! isRunning(recheck) && remove(command))
			//執行飽和策略
			reject(command);
		// 走到這裏說明線程池狀態可能是RUNNING
		// 也可能是移除線程任務失敗了(失敗的最大的可能是已經執行完畢了)
		//因爲所有存活的工作線程有可能在最後一次檢查之後已經終結,所以需要二次檢查線程池工作線程的狀態
		//這裏博主也是看了半天,大家好好體會下
		else if (workerCountOf(recheck) == 0)
			//若當前線程池工作線程數爲0,則新建一個線程並執行
			addWorker(null, false);
	}
	//步驟3:
	// 如果任務隊列已滿,就需要創建非核心線程
	// 如果新建非核心線程失敗,則執行飽和策略
	else if (!addWorker(command, false))
		reject(command);
}

上面的方法多次調用了addWorker方法,我們跟蹤進去看下源碼:

// 添加工作線程,返回true則創建和啓動工作線程成功;返回false則沒有新創建工作線程
private boolean addWorker(Runnable firstTask, boolean core) {
	retry:
	for (;;) {
		//獲取線程池對應的int值
		int c = ctl.get();
		//獲取線程池狀態
		int rs = runStateOf(c);
		// Check if queue empty only if necessary.
		if (rs >= SHUTDOWN &&
			! (rs == SHUTDOWN &&
			   firstTask == null &&
			   ! workQueue.isEmpty()))
			return false;
		for (;;) {
			//獲取工作線程數
			int wc = workerCountOf(c);
			//工作線程數超過允許的“最大線程數”則返回false
			//core爲true,“最大線程數”就是核心線程數,則表明創建核心線程數失敗
			if (wc >= CAPACITY ||
				wc >= (core ? corePoolSize : maximumPoolSize))
				return false;
			// 成功通過CAS更新工作線程數wc,則break到最外層的循環
			if (compareAndIncrementWorkerCount(c))
				break retry;
			c = ctl.get();  // Re-read ctl
			// 如果線程的狀態改變了就跳到外層循環執行
			if (runStateOf(c) != rs)
				continue retry;
			//如果CAS更新工作線程數wc失敗,則可能是併發更新導致的失敗,繼續在內層循環重試即可
			// else CAS failed due to workerCount change; retry inner loop
		}
	}
	// 標記工作線程是否啓動成功
	boolean workerStarted = false;
	//標記工作線程是否創建成功
	boolean workerAdded = false;
	//工作線程
	Worker w = null;
	try {
		//創建一個工作線程
		w = new Worker(firstTask);
		final Thread t = w.thread;
		if (t != null) {
			final ReentrantLock mainLock = this.mainLock;
			//獲取鎖
			mainLock.lock();
			try {
				// Recheck while holding lock.
				// Back out on ThreadFactory failure or if
				// shut down before lock acquired.
				int rs = runStateOf(ctl.get());
				// 再次確認"線程池狀態"
				if (rs < SHUTDOWN ||
					(rs == SHUTDOWN && firstTask == null)) {
					if (t.isAlive()) // precheck that t is startable
						throw new IllegalThreadStateException();
					//把創建的工作線程實例添加到工作線程集合
					workers.add(w);
					/更新當前工作線程的峯值容量largestPoolSize
					int s = workers.size();
					if (s > largestPoolSize)
						largestPoolSize = s;
					workerAdded = true;
				}
			} finally {
				//釋放鎖
				mainLock.unlock();
			}
			//如果加入線程池成功
			if (workerAdded) {
				//啓動線程
				t.start();
				workerStarted = true;
			}
		}
	} finally {
		//如果線程啓動失敗,則需要從工作線程集合移除對應線程
		if (! workerStarted)
			addWorkerFailed(w);
	}
	return workerStarted;
}

5、shutdown方法

線程池不用了,要關閉線程池,下面是源碼:

public void shutdown() {
    final ReentrantLock mainLock = this.mainLock;
    // 獲取鎖
    mainLock.lock();
    try {
        //校驗是否有權限。
        checkShutdownAccess();
        //設置SHUTDOWN狀態。
        advanceRunState(SHUTDOWN);
        //中斷線程池中所有空閒線程。
        interruptIdleWorkers();
        //鉤子函數
        onShutdown(); // hook for ScheduledThreadPoolExecutor
    } finally {
        //釋放鎖
        mainLock.unlock();
    }
    //嘗試終止線程池
    tryTerminate();
}

結束語

本篇詳細的分析了ThreadPoolExecutor的execute方法,耗費了不少時間。如果本文對你哪怕是有一點點的幫助,博主就感覺值了。

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