總覽
下圖是 java 線程池幾個相關類的繼承結構:
先簡單說說這個繼承結構,Executor 位于最頂層,也是最簡單的,就一個 execute(Runnable runnable) 接口方法定義。
ExecutorService 也是接口,在 Executor 接口的基礎上添加了很多的接口方法,所以一般來說我們會使用這個接口。
然后再下來一層是 AbstractExecutorService,從名字我們就知道,這是抽象類,這里實現了非常有用的一些方法供子類直接使用,之后我們再細說。
然后才到我們的重點部分 ThreadPoolExecutor 類,這個類提供了關于線程池所需的非常豐富的功能。
同在并發包中的 Executors 類,類名中帶字母 s,我們猜到這個是工具類,里面的方法都是靜態方法,如以下我們最常用的用于生成 ThreadPoolExecutor 的實例的一些方法,也就是四大線程池:
- newCachedThreadPool的cpu核心線程數為 0,最大線程數為 Integer.MAX_VALUE,keepAliveTime 為 60 秒,任務隊列采用 SynchronousQueue
這種線程池對于任務可以比較快速地完成的情況有比較好的性能。如果線程空閑了 60 秒都沒有任務,那么將關閉此線程并從線程池中移除。所以如果線程池空閑了很長時間也不會有問題,因為隨著所有的線程都會被關閉,整個線程池不會占用任何的系統資源。
- newFixedThreadPool最大線程數設置為與核心線程數相等,此時 keepAliveTime 設置為 0(因為這里它是沒用的,即使不為 0,線程池默認也不會回收 corePoolSize 內的線程),任務隊列采用 LinkedBlockingQueue,無界隊列。
- newSingleThreadExecutor生成只有一個線程的固定線程池,這個更簡單,和上面的一樣,只要設置線程數為 1 就可以了
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
}
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
new DelayedWorkQueue());
}
SynchronousQueue 是一個比較特殊的 BlockingQueue,其本身不儲存任何元素,
它有一個虛擬隊列(或虛擬棧),不管讀操作還是寫操作,
如果當前隊列中存儲的是與當前操作相同模式的線程,那么當前操作也進入隊列中等待;
如果是相反模式,則配對成功,從當前隊列中取隊頭節點。具體的信息,
可以看我的另一篇關于 BlockingQueue 的文章。
另外,由于線程池支持獲取線程執行的結果,所以,引入了 Future 接口,RunnableFuture 繼承自此接口,然后我們最需要關心的就是它的實現類 FutureTask。到這里,記住這個概念,在線程池的使用過程中,我們是往線程池提交任務(task),使用過線程池的都知道,我們提交的每個任務是實現了 Runnable 接口的,其實就是先將 Runnable 的任務包裝成 FutureTask,然后再提交到線程池。這樣,讀者才能比較容易記住 FutureTask 這個類名:它首先是一個任務(Task),然后具有 Future 接口的語義,即可以在將來(Future)得到執行的結果。
當然,線程池中的 BlockingQueue 也是非常重要的概念,如果線程數達到 corePoolSize,我們的每個任務會提交到等待隊列中,等待線程池中的線程來取任務并執行。這里的 BlockingQueue 通常我們使用其實現類 LinkedBlockingQueue、ArrayBlockingQueue 和 SynchronousQueue,每個實現類都有不同的特征,使用場景之后會慢慢分析。想要詳細了解各個 BlockingQueue 的讀者,可以參考我的前面的一篇對 BlockingQueue 的各個實現類進行詳細分析的文章。
Executor 接口
/*
* @since 1.5
* @author Doug Lea
*/
public interface Executor {
void execute(Runnable command);
}
我們可以看到 Executor 接口非常簡單,就一個 void execute(Runnable command) 方法,代表提交一個任務。
比如我們想知道執行結果、我們想知道當前線程池有多少個線程活著、已經完成了多少任務等等,這些都是這個接口的不足的地方。接下來我們要介紹的是繼承自 Executor 接口的 ExecutorService 接口,這個接口提供了比較豐富的功能,也是我們最常使用到的接口。
ExecutorService
一般我們定義一個線程池的時候,往往都是使用這個接口:
ExecutorService executor = Executors.newFixedThreadPool(args...);
ExecutorService executor = Executors.newCachedThreadPool(args...);
因為這個接口中定義的一系列方法大部分情況下已經可以滿足我們的需要了。
那么我們簡單初略地來看一下這個接口中都有哪些方法:
public interface ExecutorService extends Executor {
// 關閉線程池,已提交的任務繼續執行,不接受繼續提交新任務
void shutdown();
// 關閉線程池,嘗試停止正在執行的所有任務,不接受繼續提交新任務
// 它和前面的方法相比,加了一個單詞“now”,區別在于它會去停止當前正在進行的任務
List<Runnable> shutdownNow();
// 線程池是否已關閉
boolean isShutdown();
// 如果調用了 shutdown() 或 shutdownNow() 方法后,所有任務結束了,那么返回true
// 這個方法必須在調用shutdown或shutdownNow方法之后調用才會返回true
boolean isTerminated();
// 等待所有任務完成,并設置超時時間
// 我們這么理解,實際應用中是,先調用 shutdown 或 shutdownNow,
// 然后再調這個方法等待所有的線程真正地完成,返回值意味著有沒有超時
boolean awaitTermination(long timeout, TimeUnit unit)
throws InterruptedException;
// 提交一個 Callable 任務
<T> Future<T> submit(Callable<T> task);
// 提交一個 Runnable 任務,第二個參數將會放到 Future 中,作為返回值,
// 因為 Runnable 的 run 方法本身并不返回任何東西
<T> Future<T> submit(Runnable task, T result);
// 提交一個 Runnable 任務
Future<?> submit(Runnable task);
// 執行所有任務,返回 Future 類型的一個 list
<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
throws InterruptedException;
// 也是執行所有任務,但是這里設置了超時時間
<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks,
long timeout, TimeUnit unit)
throws InterruptedException;
// 只有其中的一個任務結束了,就可以返回,返回執行完的那個任務的結果
<T> T invokeAny(Collection<? extends Callable<T>> tasks)
throws InterruptedException, ExecutionException;
// 同上一個方法,只有其中的一個任務結束了,就可以返回,返回執行完的那個任務的結果,
// 不過這個帶超時,超過指定的時間,拋出 TimeoutException 異常
<T> T invokeAny(Collection<? extends Callable<T>> tasks,
long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException;
}
這些方法都很好理解,一個簡單的線程池主要就是這些功能,能提交任務,能獲取結果,能關閉線程池,這也是為什么我們經常用這個接口的原因。
FutureTask
在繼續往下層介紹 ExecutorService 的實現類之前,我們先來說說相關的類 FutureTask。
Future Runnable
\ /
\ /
RunnableFuture
|
|
FutureTask
FutureTask 通過 RunnableFuture 間接實現了 Runnable 接口,
所以每個 Runnable 通常都先包裝成 FutureTask,
然后調用 executor.execute(Runnable command) 將其提交給線程池
我們知道,Runnable 的 void run() 方法是沒有返回值的,所以,通常,如果我們需要的話,會在 submit 中指定第二個參數作為返回值:
<T> Future<T> submit(Runnable task, T result);
其實到時候會通過這兩個參數,將其包裝成 Callable。它和 Runnable 的區別在于 run() 沒有返回值,而 Callable 的 call() 方法有返回值,同時,如果運行出現異常,call() 方法會拋出異常。
public interface Callable<V> {
V call() throws Exception;
}
在這里,就不展開說 FutureTask 類了,因為本文篇幅本來就夠大了,這里我們需要知道怎么用就行了。
下面,我們來看看 ExecutorService 的抽象實現 AbstractExecutorService 。
AbstractExecutorService
AbstractExecutorService 抽象類派生自 ExecutorService 接口,然后在其基礎上實現了幾個實用的方法,這些方法提供給子類進行調用。
這個抽象類實現了 invokeAny 方法和 invokeAll 方法,這里的兩個 newTaskFor 方法也比較有用,用于將任務包裝成 FutureTask。定義于最上層接口 Executor中的 void execute(Runnable command) 由于不需要獲取結果,不會進行 FutureTask 的包裝。
需要獲取結果(FutureTask),用 submit 方法,不需要獲取結果,可以用 execute 方法。
下面,我將一行一行源碼地來分析這個類,跟著源碼來看看其實現吧:
Tips: invokeAny 和 invokeAll 方法占了這整個類的絕大多數篇幅,這里選擇適當跳過,
因為它們可能在你的實踐中使用的頻次比較低,而且它們不帶有承前啟后的作用,不用擔心會漏掉什么導致看不懂后面的代碼。
public abstract class AbstractExecutorService implements ExecutorService {
// RunnableFuture 是用于獲取執行結果的,我們常用它的子類 FutureTask
// 下面兩個 newTaskFor 方法用于將我們的任務包裝成 FutureTask 提交到線程池中執行
protected <T> RunnableFuture<T> newTaskFor(Runnable runnable, T value) {
return new FutureTask<T>(runnable, value);
}
protected <T> RunnableFuture<T> newTaskFor(Callable<T> callable) {
return new FutureTask<T>(callable);
}
// 提交任務
public Future<?> submit(Runnable task) {
if (task == null) throw new NullPointerException();
// 1. 將任務包裝成 FutureTask
RunnableFuture<Void> ftask = newTaskFor(task, null);
// 2. 交給執行器執行,execute 方法由具體的子類來實現
// 前面也說了,FutureTask 間接實現了Runnable 接口。
execute(ftask);
return ftask;
}
public <T> Future<T> submit(Runnable task, T result) {
if (task == null) throw new NullPointerException();
// 1. 將任務包裝成 FutureTask
RunnableFuture<T> ftask = newTaskFor(task, result);
// 2. 交給執行器執行
execute(ftask);
return ftask;
}
public <T> Future<T> submit(Callable<T> task) {
if (task == null) throw new NullPointerException();
// 1. 將任務包裝成 FutureTask
RunnableFuture<T> ftask = newTaskFor(task);
// 2. 交給執行器執行
execute(ftask);
return ftask;
}
}
到這里,我們發現,這個抽象類包裝了一些基本的方法,它們都沒有真正開啟線程來執行任務,它們都只是在方法內部調用了 execute 方法,所以最重要的 execute(Runnable runnable) 方法還沒出現,需要等具體執行器來實現這個最重要的部分,這里我們要說的就是 ThreadPoolExecutor 類了。
ThreadPoolExecutor
ThreadPoolExecutor 是 JDK 中的線程池實現,這個類實現了一個線程池需要的各個方法,它實現了任務提交、線程管理、監控等等方法。
我們可以基于它來進行業務上的擴展,以實現我們需要的其他功能,比如實現定時任務的類 ScheduledThreadPoolExecutor 就繼承自 ThreadPoolExecutor。當然,這不是本文關注的重點,下面,還是趕緊進行源碼分析吧。
首先看看類的結構:
public class ThreadPoolExecutor extends AbstractExecutorService {
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
//Integer.SIZE=32,32-3意味著前三位用于存放線程狀態,后29位用于存放線程數
//很多初學者很喜歡在自己的代碼中寫很多 29 這種數字,或者某個特殊的字符串,然后分布在各個地方,這是非常糟糕的
private static final int COUNT_BITS = Integer.SIZE - 3;
//1 << COUNT_BITS = 0010 0000 0000 0000 0000 0000 0000 0000
//(1 << COUNT_BITS) - 1 = 0001 1111 1111 1111 1111 1111 1111 1111
//得到29個1,也就是說線程池的最大線程數是 2^29-1=536870911
// 以我們現在計算機的實際情況,這個數量還是夠用的
private static final int CAPACITY = (1 << COUNT_BITS) - 1;
// 1=0000 0000 0000 0000 0000 0000 0000 0001
//1反碼=1111 1111 1111 1111 1111 1111 1111 1110
//-1=1補碼=反碼+1=1111 1111 1111 1111 1111 1111 1111 1111
//-1 << COUNT_BITS= -1左移29次=1110 0000 0000 0000 0000 0000 0000 0000
//也就是32的高3位為線程池狀態
private static final int RUNNING = -1 << COUNT_BITS;
// 0000 0000 0000 0000 0000 0000 0000 0000
private static final int SHUTDOWN = 0 << COUNT_BITS;
//0010 0000 0000 0000 0000 0000 0000 0000
private static final int STOP = 1 << COUNT_BITS;
//0100 0000 0000 0000 0000 0000 0000 0000
private static final int TIDYING = 2 << COUNT_BITS;
//0110 0000 0000 0000 0000 0000 0000 0000
private static final int TERMINATED = 3 << COUNT_BITS;
// 將整數c的低29位改成0,得到線程池的狀態
private static int runStateOf(int c) {
return c & ~CAPACITY;
}
//將整數c的高3位改為0,得到線程池中的線程數
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;
}
//c < SHUTDOWN也只有RUNNING,這個方法是判斷線程池是否運行中
private static boolean isRunning(int c) {
return c < SHUTDOWN;
}
//cas比較設置增加ctl值,ctl值錢3位為狀態,后29位為線程數
private boolean compareAndIncrementWorkerCount(int expect) {
return ctl.compareAndSet(expect, expect + 1);
}
//cas比較設置減少ctl值,ctl值錢3位為狀態,后29位為線程數
private boolean compareAndDecrementWorkerCount(int expect) {
return ctl.compareAndSet(expect, expect - 1);
}
//循環設置ctl減少,直到更新成功
private void decrementWorkerCount() {
do {
} while (!compareAndDecrementWorkerCount(ctl.get()));
}
//等待隊列
private final BlockingQueue<Runnable> workQueue;
//全局鎖
private final ReentrantLock mainLock = new ReentrantLock();
//工作線程所在集合
private final HashSet<Worker> workers = new HashSet<Worker>();
//這個是為了實現wait..notify類似效果,由condition.await和signal實現,這個可以針對單個喚醒
private final Condition termination = mainLock.newCondition();
//記錄工作worker最大值,就是workers的set集合最大個數
private int largestPoolSize;
//執行完成的任務數量,拋異常也算
private long completedTaskCount;
//創建線程的工程
private volatile ThreadFactory threadFactory;
//在執行飽和或關閉時調用處理程序。拒絕策略
private volatile RejectedExecutionHandler handler;
//當線程數大于內核數時,這是多余的空閑線程將在終止之前等待新任務的最長時間
//當線程空閑時間達到keepAliveTime時,線程會退出,直到線程數量=corePoolSize
//如果allowCoreThreadTimeout=true,則會直到線程數量=0
private volatile long keepAliveTime;
//默認false,cpu核心線程閑置的話也會一直保持活著狀態,如果true,核心線程會以keepAliveTime等待獲取任務
//允許核心線程超時
private volatile boolean allowCoreThreadTimeOut;
//cpu核數
//核心線程會一直存活,即使沒有任務需要執行
//當線程數小于核心線程數時,即使有線程空閑,線程池也會優先創建新線程處理
// 設置allowCoreThreadTimeout=true(默認false)時,核心線程會超時關閉
private volatile int corePoolSize;
//線程池最大線程數,實際受CAPACITY限制
private volatile int maximumPoolSize;
//默認拒絕策略,當隊列滿了,線程數據也達到了最大,則執行拒絕策略,默認直接拋出異常
private static final RejectedExecutionHandler defaultHandler =
new AbortPolicy();
private static final RuntimePermission shutdownPerm =
new RuntimePermission("modifyThread");
/* The context to be used when executing the finalizer, or null. */
private final AccessControlContext acc;
//worker是工作者,里面維護線程讓線程執行任務
private final class Worker
extends AbstractQueuedSynchronizer
implements Runnable {
private static final long serialVersionUID = 6138294804551838833L;
//運行任務的線程
final Thread thread;
//初始化任務,可以是空
Runnable firstTask;
//完成的任務數量
volatile long completedTasks;
//初始化worker,初始任務,可以為空
Worker(Runnable firstTask) {
//設置狀態
//把狀態位設置成-1,這樣任何線程都不能得到Worker的鎖,除非調用了unlock方法。這個unlock方法會在runWorker方法中一開始就調用,
//這是為了確保Worker構造出來之后,沒有任何線程能夠得到它的鎖,除非調用了runWorker之后,其他線程才能獲得Worker的鎖
setState(-1);
this.firstTask = firstTask;
//線程工廠創建線程
this.thread = getThreadFactory().newThread(this);
}
//worker執行任務,在addWorker里新增成功后啟動線程
public void run() {
runWorker(this);
}
}
接著,我們來看看線程池實現中的幾個概念和處理流程。
我們先回顧下提交任務的幾個方法:
public Future<?> submit(Runnable task) {
if (task == null) throw new NullPointerException();
RunnableFuture<Void> ftask = newTaskFor(task, null);
execute(ftask);
return ftask;
}
public <T> Future<T> submit(Runnable task, T result) {
if (task == null) throw new NullPointerException();
RunnableFuture<T> ftask = newTaskFor(task, result);
execute(ftask);
return ftask;
}
public <T> Future<T> submit(Callable<T> task) {
if (task == null) throw new NullPointerException();
RunnableFuture<T> ftask = newTaskFor(task);
execute(ftask);
return ftask;
}
當然,上圖沒有考慮隊列是否有界,提交任務時隊列滿了怎么辦?什么情況下會創建新的線程?提交任務時線程池滿了怎么辦?空閑線程怎么關掉?這些問題下面我們會一一解決。
我們經常會使用 Executors 這個工具類來快速構造一個線程池,對于初學者而言,這種工具類是很有用的,開發者不需要關注太多的細節,只要知道自己需要一個線程池,僅僅提供必需的參數就可以了,其他參數都采用作者提供的默認值。
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
這里先不說有什么區別,它們最終都會導向這個構造方法:
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
//corePoolSize:cpu核數不能小于0
//maximumPoolSize:線程池做大線程數不能小于等于0并不能小于cpu核心數
//keepAliveTime:當線程數大于內核數時,這是多余的空閑線程將在終止之前等待新任務的最長時間,也不能小于0
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;
}
在這里,介紹下線程池中的各個狀態和狀態變化的轉換過程:
- RUNNING:這個沒什么好說的,這是最正常的狀態:接受新的任務,處理等待隊列中的任務
- SHUTDOWN:不接受新的任務提交,但是會繼續處理等待隊列中的任務
- STOP:不接受新的任務提交,不再處理等待隊列中的任務,中斷正在執行任務的線程
- TIDYING:所有的任務都銷毀了,workCount 為 0。線程池的狀態在轉換為 TIDYING 狀態時,會執行鉤子方法 terminated()
- TERMINATED:terminated() 方法結束后,線程池的狀態就會變成這個
RUNNING 定義為 -1,SHUTDOWN 定義為 0,其他的都比 0 大,所以等于 0 的時候不能提交任務,大于 0 的話,連正在執行的任務也需要中斷。
看了這幾種狀態的介紹,讀者大體也可以猜到十之八九的狀態轉換了,各個狀態的轉換過程有以下幾種:
- RUNNING -> SHUTDOWN:當調用了 shutdown() 后,會發生這個狀態轉換,這也是最重要的
- (RUNNING or SHUTDOWN) -> STOP:當調用 shutdownNow() 后,會發生這個狀態轉換,這下要清楚shutDown() 和 shutDownNow() 的區別了
- SHUTDOWN -> TIDYING:當任務隊列和線程池都清空后,會由 SHUTDOWN 轉換為 TIDYING
- STOP -> TIDYING:當任務隊列清空后,發生這個轉換
- TIDYING -> TERMINATED:這個前面說了,當 terminated() 方法結束后
另外,我們還要看看一個內部類 Worker,因為 Doug Lea 把線程池中的線程包裝成了一個個 Worker,翻譯成工人,就是線程池中做任務的線程。所以到這里,我們知道任務是 Runnable(內部變量名叫 task 或 command),線程是 Worker。
Worker 這里又用到了抽象類 AbstractQueuedSynchronizer。題外話,AQS 在并發中真的是到處出現,而且非常容易使用,寫少量的代碼就能實現自己需要的同步方式(對 AQS 源碼感興趣的讀者請參看我之前寫的幾篇文章http://www.lxweimin.com/p/54d372425e54)。
//worker是工作者,里面維護線程讓線程執行任務
private final class Worker
extends AbstractQueuedSynchronizer
implements Runnable {
private static final long serialVersionUID = 6138294804551838833L;
//運行任務的線程
final Thread thread;
//初始化任務,可以是空
Runnable firstTask;
//完成的任務數量
volatile long completedTasks;
//初始化worker,初始任務,可以為空
Worker(Runnable firstTask) {
//設置狀態
//把狀態位設置成-1,這樣任何線程都不能得到Worker的鎖,除非調用了unlock方法。這個unlock方法會在runWorker方法中一開始就調用,
//這是為了確保Worker構造出來之后,沒有任何線程能夠得到它的鎖,除非調用了runWorker之后,其他線程才能獲得Worker的鎖
setState(-1);
this.firstTask = firstTask;
//線程工廠創建線程
this.thread = getThreadFactory().newThread(this);
}
//worker執行任務,在addWorker里新增成功后啟動線程
public void run() {
runWorker(this);
}
...// 其他幾個方法沒什么好看的,就是用 AQS 操作,來獲取這個線程的執行權,用了獨占鎖
worker的加鎖解鎖機制是基于AQS框架的,要完全弄明白它的加鎖解鎖機制請看AQS框架的實現,在這里只是簡單介紹一下:
//嘗試加鎖方法,將狀態從0設置為1;如果不是0則加鎖失敗,在worker線程沒有啟動前是-1狀態,無法加鎖
//該方法重寫了父類AQS的同名方法
protected boolean tryAcquire(int unused) {
if (compareAndSetState(0, 1)) {
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
return false;
}
//嘗試釋放鎖的方法,直接將state置為0
//該方法重寫了父類AQS的同名方法
protected boolean tryRelease(int unused) {
setExclusiveOwnerThread(null);
setState(0);
return true;
}
//注意:tryAcquire與tryRelease是重寫了AQS父類的方法,且不可以直接調用,它們被以下方法調用實現加鎖解鎖操作
//加鎖:acquire法是它父類AQS類的方法,會調用tryAcquire方法加鎖
public void lock() { acquire(1); }
//嘗試加鎖
public boolean tryLock() { return tryAcquire(1); }
//解鎖:release方法是它父類AQS類的方法,會調用tryRelease方法
public void unlock() { release(1); }
//返回鎖狀態
public boolean isLocked() { return isHeldExclusively(); }
默認工廠DefaultThreadFactory
線程的創建看下defaultThreadFactory,當然是在Executors類創建
public static ThreadFactory defaultThreadFactory() {
return new DefaultThreadFactory();
}
static class DefaultThreadFactory implements ThreadFactory {
//線程池編號
private static final AtomicInteger poolNumber = new AtomicInteger(1);
//線程池中線程所屬線程組
private final ThreadGroup group;
//線程池中線程編號
private final AtomicInteger threadNumber = new AtomicInteger(1);
//線程名稱前綴
private final String namePrefix;
DefaultThreadFactory() {
SecurityManager s = System.getSecurityManager();
group = (s != null) ? s.getThreadGroup() :
Thread.currentThread().getThreadGroup();
//設置線程名稱為"pool-線程池的編號-thread-線程的編號"
namePrefix = "pool-" +
poolNumber.getAndIncrement() +
"-thread-";
}
//創建新的線程
public Thread newThread(Runnable r) {
Thread t = new Thread(group, r,
namePrefix + threadNumber.getAndIncrement(),
0);
//設置為非守護線程
if (t.isDaemon())
t.setDaemon(false);
//設置優先級為NORMAL為5
if (t.getPriority() != Thread.NORM_PRIORITY)
t.setPriority(Thread.NORM_PRIORITY);
return t;
}
}
一般我們最好不要用默認的線程池,可以繼承該類,給線程指定一個識別度高的名字,出了問題好排查;
怎么去創建一個worker
worker是線程池執行任務的線程
- 如果線程池處于TERMINATED、STOP、TIDYING和(SHUTDOWN+任務隊列為空)是沒必要創建線程了
- core是true則線程數大于等于cpu核心線程數沒必要創建線程或者core是false則大于等于線程最大數也沒必要創建線程
- cas比較線程數+1成功則創建worker并且初始化線程
- 線程添加到線程集合并記錄線程集合最大值,啟動線程
- 線程啟動失敗則從線程集合移除線程
- 如果當前線程池狀態處于RUNNING和(SHUTDOWN+任務隊列不為空)則不能去停止線程池,如果處于TIDYING或TERMINATED就沒必要再去關閉線程池了
- 如果線程池線程不為0,則遍歷線程集合,判斷是否被中斷過是否是閑置,如果是發出中斷,喚醒獲取任務阻塞的線程,如果為0,設置TIDYING,成功則設置成TERMINATED,關閉線程池
先看看流程圖:
這個方法非常重要 addWorker(Runnable firstTask, boolean core) 方法,我們看看它是怎么創建新的線程的:
private boolean addWorker(Runnable firstTask, boolean core) {
//標志,和break retry以及continue retry聯合使用
//break retry結束內存循環直接往retry:下方代碼,不再執行for
//continue retry結束內存循環直接往retry:下方代碼,重新執行for
retry:
//自旋
for (; ; ) {
//或許線程池狀態整數
int c = ctl.get();
//得到線程池狀態
// 線程池狀態只有高3位
int rs = runStateOf(c);
// 這個非常不好理解
// 如果線程池已關閉,并滿足以下條件之一,那么不創建新的 worker:
// 1. 線程池狀態大于 SHUTDOWN,其實也就是 STOP, TIDYING, 或 TERMINATED
// 簡單分析下:
// 還是狀態控制的問題,當線程池處于 SHUTDOWN 的時候,不允許提交任務,但是已有的任務繼續執行
// 當狀態大于 SHUTDOWN 時,不允許提交任務,且中斷正在執行的任務
// 多說一句:如果線程池處于 SHUTDOWN,但是 firstTask 為 null,且 workQueue 非空,那么是允許創建 worker 的
// 這是因為 SHUTDOWN 的語義:不允許提交新的任務,但是要把已經進入到 workQueue 的任務執行完,所以在滿足條件的基礎上,是允許創建新的 Worker 的
if (rs >= SHUTDOWN &&
!(rs == SHUTDOWN &&
firstTask == null &&
!workQueue.isEmpty()))
return false;
for (; ; ) {
//獲得線程池的線程數
int wc = workerCountOf(c);
//線程數大于等于CAPACITY(2^29-1),創建worker失敗返回
//當core是true則線程數大于等于核心數就創建worker失敗返回,
// 當是false則線程數大于等于線程最大數量就創建worker失敗返回
if (wc >= CAPACITY ||
wc >= (core ? corePoolSize : maximumPoolSize))
return false;
//cas比較設置當前線程數+1,成功(代表線程數量匹配上,并要創建新線程所以+1)退出自旋返回到retry:
//ctl的值就+1了
if (compareAndIncrementWorkerCount(c))
break retry;
//比較不成功,說明線程數可能已經被其它線程修改過了,重新讀一次
c = ctl.get();
//得到的線程池狀態跟初始進來的狀態不一樣,則continue retry到retry:重新執行for
if (runStateOf(c) != rs)
continue retry;
}
}
//到這里已經具備創建worker的條件,下面開始創建worker
//worker啟動標志
boolean workerStarted = false;
//worker已經被添加標志
boolean workerAdded = false;
Worker w = null;
try {
//創建一新的Worker,在worker構造方法里對線程池的狀態設置為-1
//并且線程工程創建線程,有第一個任務也會設置進去
w = new Worker(firstTask);
//在worker創建時初始化的線程
final Thread t = w.thread;
//線程初始化成功
if (t != null) {
final ReentrantLock mainLock = this.mainLock;
//這是整個線程池的全局鎖,持有這個鎖才能讓下面的操作順理成章
//因為關閉一個線程池需要這個鎖,至少我持有鎖的期間,線程池不會被關閉
mainLock.lock();
try {
//ctl這個值在前面cas已經設置+1
//重新獲取線程池的狀態
int rs = runStateOf(ctl.get());
//rs < SHUTDOWN:處于RUNNING,正常情況
//(rs == SHUTDOWN && firstTask == null):處于SHUTDOWN不接受新的任務,但是會執行等待隊列的任務
if (rs < SHUTDOWN ||
(rs == SHUTDOWN && firstTask == null)) {
//新增的worker里的線程可不能是已經啟動的
if (t.isAlive()) // precheck that t is startable
throw new IllegalThreadStateException();
//到這里,worker已經初始化好,并添加到hashSet里
workers.add(w);
//查看worker集合的大小
int s = workers.size();
//largestPoolSize是用于記錄worker中個數的最大值
//當worker集合數量已經大于它,則將其調整為當前worker集合數量
if (s > largestPoolSize)
largestPoolSize = s;
//表示worker添加成功
workerAdded = true;
}
} finally {
//worker一系列添加后釋放鎖
mainLock.unlock();
}
//如果worker已經添加成,啟動線程會調用runWorker方法,并設置線程已經啟動標志
if (workerAdded) {
t.start();
workerStarted = true;
}
}
} finally {
//worker線程啟動失敗
if (!workerStarted)
addWorkerFailed(w);
}
//返回線程是否啟動標志
return workerStarted;
}
簡單看下 addWorkFailed 的處理:
tryTerminate();方法后面統一講,因為很多地方調用它
//添加worker失敗
private void addWorkerFailed(Worker w) {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
//線程啟動失敗,但是worker已經創建好了,則將創建好的worker從集合移除
if (w != null)
workers.remove(w);
//死循環,cas比較去將線程數整數減一,直到減成功,ctl的值就減一
decrementWorkerCount();
//重新檢查是否終止,以防該worker的存在阻止了終止
tryTerminate();
} finally {
//釋放鎖
mainLock.unlock();
}
}
cas更新線程數,線程數減一
//循環設置ctl減少,直到更新成功
private void decrementWorkerCount() {
do {
} while (!compareAndDecrementWorkerCount(ctl.get()));
}
每個worker怎么獲取任務呢?
- 自旋獲取線程狀態,線程狀態不處于RUNNING或者不處于(SHUTDOWN+任務隊列不為空),則線程數減一,任務返回空。
- 自旋獲取線程狀態,線程狀態處于RUNNING或者處于(SHUTDOWN+任務隊列不為空),則當前線程數如果大于最大線程數并且任務隊列時刻,或者當前線程數大于核心線程數并且超時并且隊列是空,那么cas設置線程減一,繼續自旋,否則指定keepAliveTime超時時間去讀取任務或者直接阻塞讀取任務。
- 讀取任務的時候會被阻塞,如果tryTerminal里的中斷信號發出,這里就會識別,則拋出中斷異常,線程醒過來,設置超時標識,繼續進行自旋。
- 如果讀取的任務不為空,則直接返回任務。
流程圖如下:
// 如果發生了以下四件事中的任意一件,那么Worker需要被回收:
// 1. Worker個數比線程池最大大小要大
// 2. 線程池處于STOP狀態
// 3. 線程池處于SHUTDOWN狀態并且阻塞隊列為空
// 4. 使用超時時間從阻塞隊列里拿數據,并且超時之后沒有拿到數據(allowCoreThreadTimeOut || workerCount > corePoolSize)
private Runnable getTask() {
// 如果使用超時時間并且也沒有拿到任務的標識
boolean timedOut = false;
for (; ; ) {
int c = ctl.get();
int rs = runStateOf(c);
//worker減一是因為前面添加worker的時候啟動線程已經添加成功,而這盤點不符合執行任務,所以把添加的worker數量減掉
//在processWorkerExit進行回收
// 如果線程池是SHUTDOWN狀態并且阻塞隊列為空的話,worker數量減一,
// 直接返回null(SHUTDOWN狀態還會處理阻塞隊列任務,但是阻塞隊列為空的話就結束了),
// 如果線程池是STOP狀態的話,worker數量減一,
// 直接返回null(STOP狀態不處理阻塞隊列任務)
if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
decrementWorkerCount();
return null;
}
//當前線程數
int wc = workerCountOf(c);
//如果設置了allowCoreThreadTimeOut則可能超時
//標記從隊列中取任務時是否設置超時時間,
// 如果為true說明這個worker可能需要回收,
// 為false的話這個worker會一直存在,并且阻塞當前線程等待阻塞隊列中有數據
// allowCoreThreadTimeOut屬性默認為false,表示線程池中的核心線程在閑置狀態下還保留在池中;
// 如果是true表示核心線程使用keepAliveTime這個參數來作為超時時間
// 如果worker數量比基本大小要大的話,timed就為true,需要進行回收worker
// timed變量用于判斷是否需要進行超時控制。
// allowCoreThreadTimeOut默認是false,也就是核心線程不允許進行超時;
// wc > corePoolSize,表示當前線程池中的線程數量大于核心線程數量;
// 對于超過核心線程數量的這些線程,需要進行超時控制
boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
//4中情況分析:
//第一:wc > maximumPoolSize && wc > 1 大于了maximumPoolSize必然大于1,所以不獲取任務
//第二:wc > maximumPoolSize && workQueue.isEmpty() 阻塞隊列的沒有任務了,也就沒必要獲取
//第三:(timed && timedOut) && wc>1超時了,有線程在運行,就沒必要獲取任務
//第四:(timed && timedOut) && workQueue.isEmpty()超時了,任務隊列沒任務,則沒必要獲取
//(timed && timedOut)表示當前線程大于核心數并且前面獲取任務poll超時沒獲取到任務,則timedOut=true,
//這個就體現了keepAliveTime的用處,超時沒獲取到任務,并且當前線程超高了cpu核心,因為沒獲取到任務worker就閑置
//所以這里線程數減一,在外面進行worker的回收
if ((wc > maximumPoolSize || (timed && timedOut))
&& (wc > 1 || workQueue.isEmpty())) {
//線程數減一
if (compareAndDecrementWorkerCount(c))
//不用執行任務
return null;
continue;
}
try {
//poll:從BlockingQueue取出一個隊首的對象,如果在指定時間內,隊列一旦有數據可取,則立即返回隊列中的數據。
// 否則直到時間超時還沒有數據可取,返回失敗。
//take:取走BlockingQueue里排在首位的對象,若BlockingQueue為空,
// 阻斷進入等待狀態直到BlockingQueue有新的數據被加入
//設置了allowCoreThreadTimeOut或者當前線程已經大于cpu核心數則以keepAliveTime超時時間獲取任務
Runnable r = timed ?
workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
workQueue.take();
//任務取到,返回任務
if (r != null)
return r;
//到這里任務是空,設置超時標識
timedOut = true;
} catch (InterruptedException retry) {
timedOut = false;
}
}
}
每個worker是怎么執行任務的呢
添加worker成功后會去啟動線程,這時候調用ThreadPoolExecutor的run方法,然后執行runWorker方法。
那接下來看下runWorker方法
- 先釋放當前當前worker的鎖,這樣支持獲取任務的時候可以多個線程去搶占woker,在if (!t.isInterrupted() && w.tryLock())才能獲取到鎖,如果獲取到鎖說明線程是閑置狀態
- 獲取任務
- 獲取到任務,進行全局加鎖
- 如果線程池已經處于STOP狀態以上線程卻沒有終端,則中斷當前線程,也就是不去獲取任務了,但是會繼續執行當前任務,然后完成任務數量+1,解鎖,異常完成任務標識設置為false。
- 回收worker。
先看下流程圖:
// 此方法由 worker 線程啟動后調用,這里用一個 while 循環來不斷地從等待隊列中獲取任務并執行
// 前面說了,worker 在初始化的時候,可以指定 firstTask,那么第一個任務也就可以不需要從隊列中獲取
final void runWorker(Worker w) {
Thread wt = Thread.currentThread();
// 該線程的第一個任務(如果有的話)
Runnable task = w.firstTask;
w.firstTask = null;
//由于在創建Worker的時候設置狀態是-1,其它線程不能得到worker的鎖。這里釋放后其它線程可以獲得worker的鎖
//在getask的時候可以支持中斷,如果這邊不unlock的話,在if (!t.isInterrupted() && w.tryLock())就一直認為它是忙的狀態
//則無法打斷因為獲取任務而阻塞的線程,它只是在獲取任務而不是執行任務,也是閑置的線程
w.unlock();
//是否正常結束任務標識
boolean completedAbruptly = true;
try {
// 如果worker中的任務不為空,否則使用getTask獲得任務。一直死循環,除非得到的任務為空才退出
//當然getTask如果沒拿到任務會一直阻塞直到拿到任務
while (task != null || (task = getTask()) != null) {
// 如果拿到了任務,給自己上鎖,表示當前Worker已經要開始執行任務了,
// 已經不是閑置Worker(閑置Worker的解釋請看下面的線程池關閉)
w.lock();
// 在執行任務之前先做一些處理。
// 1. 如果線程池已經大于等于STOP狀態并且當前線程沒有被中斷,中斷線程
// 2. 如果線程池還處于RUNNING或SHUTDOWN狀態,并且當前線程已經被中斷了,
// 重新檢查一下線程池狀態,如果處于STOP狀態并且沒有被中斷,那么中斷線程
if ((runStateAtLeast(ctl.get(), STOP) ||
(Thread.interrupted() &&
runStateAtLeast(ctl.get(), STOP))) &&
!wt.isInterrupted())
wt.interrupt();
try {
// 任務執行前需要做什么,ThreadPoolExecutor是個空實現
beforeExecute(wt, task);
Throwable thrown = null;
try {
// 真正的開始執行任務,調用的是run方法,而不是start方法。這里run的時候可能會被中斷,比如線程池調用了shutdownNow方法
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 {
// 任務執行結束需要做什么,ThreadPoolExecutor是個空實現
afterExecute(task, thrown);
}
} finally {
task = null;
// 記錄執行任務的個數
w.completedTasks++;
// 執行完任務之后,解鎖,Worker變成閑置Worker
w.unlock();
}
}
//正常執行完任務,改成false
completedAbruptly = false;
} finally {
// 回收Worker
processWorkerExit(w, completedAbruptly);
}
}
任務執行的worker應該怎么處理?
會對worker進行一個回收
- 判斷在執行任務的時候有沒有異常,如果有則會將線程數量通過cas比較減一,因為添加worker的時候已加入隊列,失敗了就要減掉
- 任務正常執行,統計線程完成任務數,移除當前worker,嘗試關閉線程池
- 若線程池還處于RUNING和(SHUTDOWN+任務隊列非空),則還不能關閉線程池,再判斷運行任務的時候是否正常運行,是就開始設置線程數量最小值,設置了allowCoreThreadTimeOut最小值為0,沒設置則cpu核心數,比較最小值,如果最小值為0并且還有任務為完成,則最小值改成1(執行任務)。當前線程數大于等于最小線程,則不用再創建worker,如果當前線程數量小于最小值添加新worker
只有以下幾種情況才會在回收worker還會補償創建worker:
- 線程池處于RUNING和(SHUTDOWN+任務隊列非空)則任務還沒執行完,前面執行任務又失敗worker被回收,這里需要補償創建
- 線程池處于RUNING和(SHUTDOWN+任務隊列非空)則任務還沒執行完,前面執行任務成功,但是當前線程數小于線程數量最小值,這里需要補償創建
流程圖如下:
//回收Worker
private void processWorkerExit(Worker w, boolean completedAbruptly) {
// 如果Worker沒有正常結束流程調用processWorkerExit方法,worker數量減一。
// 如果是正常結束的話,在getTask方法里worker數量已經減一了
if (completedAbruptly)
decrementWorkerCount();
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
//退出前,將本線程已完成的任務數量,添加到已經完成任務的總數中
completedTaskCount += w.completedTasks;
// 線程池的worker集合刪除掉需要回收的Worker
workers.remove(w);
} finally {
mainLock.unlock();
}
// 嘗試結束線程池
tryTerminate();
int c = ctl.get();
// 如果線程池還處于RUNNING或者SHUTDOWN狀態
if (runStateLessThan(c, STOP)) {
//如果非異常狀況completedAbruptly=false,也就是沒有獲取到可執行的任務,則獲取線程池允許的最小線程數,
// 如果allowCoreThreadTimeOut為true說明允許核心線程超時,則最小線程數為0,否則最小線程數為corePoolSize;
if (!completedAbruptly) {
int min = allowCoreThreadTimeOut ? 0 : corePoolSize;
//如果allowCoreThreadTimeOut=true,且任務隊列有任務要執行,則將最最小線程數設置為1
if (min == 0 && !workQueue.isEmpty())
min = 1;
//如果當前線程數大于等于最小線程數,則直接返回
if (workerCountOf(c) >= min)
// 不需要新開一個Worker
return;
}
// 新開一個Worker代替原先的Worker
// 新開一個Worker需要滿足以下3個條件中的任意一個:
// 1. 獲取執行的任務發生了異常
// 2. Worker數量比線程池基本大小要小
// 3. 阻塞隊列不空但是沒有任何Worker在工作
addWorker(null, false);
}
}
execute
先看下流程圖:
- 如果當前線程數小于核心數量,直接創建worker
- 如果線程池狀態是RUNNING并且任務加到等待隊列成功,還要重新確認一次線程池的狀態,若還是RUNNING,則當前線程數等于0的話直接創建worker,如果不是RUNNING并且隊列移除任務成功,執行拒絕策略,默認拋出異常
- 如果 workQueue 隊列滿了,以 maximumPoolSize 為界創建新的 worker, 如果失敗,說明當前線程數已經達到 maximumPoolSize,執行拒絕策略
有了上面的這些基礎后,我們終于可以看看 ThreadPoolExecutor 的 execute 方法了,前面源碼分析的時候也說了,各種方法都最終依賴于 execute 方法,也就是線程池執行任務的入口:
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
// 前面說的那個表示 “線程池狀態” 和 “線程數” 的整數
int c = ctl.get();
//得到線程池的線程數,如果線程數小于cpu核心數
//新增worker加入hashSet成功,就直接返回了
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true))
return;
//新增worker加入hashSet失敗,則重新確認ctl的值
c = ctl.get();
}
//到這里添加worker失敗了,需要把任務放到等待隊列
//線程池還在RUNNING狀態,阻塞隊列也沒滿的情況,加到阻塞隊列里
if (isRunning(c) && workQueue.offer(command)) {
/* 這里面說的是,如果任務進入了 workQueue,我們是否需要開啟新的線程
* 因為線程數在 [0, corePoolSize) 是無條件開啟新的線程
* 如果線程數已經大于等于 corePoolSize,那么將任務添加到隊列中,然后進到這里
*/
int recheck = ctl.get();
// 如果線程池已不處于 RUNNING 狀態,那么移除已經入隊的這個任務,并且執行拒絕策略
if (!isRunning(recheck) && remove(command))
//默認的拒絕策略直接拋異常
reject(command);
// 如果線程池還是 RUNNING 的,并且線程數為 0,那么開啟新的線程
// 到這里,我們知道了,這塊代碼的真正意圖是:擔心任務提交到隊列中了,但是線程都關閉了
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
// 如果 workQueue 隊列滿了,那么進入到這個分支
// 以 maximumPoolSize 為界創建新的 worker,
// 如果失敗,說明當前線程數已經達到 maximumPoolSize,執行拒絕策略
else if (!addWorker(command, false))
reject(command);
}
如果等待隊列滿了后對任務怎么處理的
是有四種不同的拒絕策略
final void reject(Runnable command) {
handler.rejectedExecution(command, this);
}
- AbortPolicy(默認策略)直接拋出異常拒絕接收任務
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
throw new RejectedExecutionException("Task " + r.toString() +
" rejected from " +
e.toString());
}
- CallerRunsPolicy如果線程池在運行,以當前線程完成任務
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
if (!e.isShutdown()) {
r.run();
}
}
- DiscardOldestPolicy丟棄等待最久的任務,繼續執行其它任務
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
if (!e.isShutdown()) {
e.getQueue().poll();
e.execute(r);
}
}
- DiscardPolicy不做任何處理,新的任務直接忽略
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
}
線程池的關閉
線程池的關閉有兩個方法shutdown() 與shutdownNow() ;
shutdown會將線程池狀態設置為SHUTDOWN狀態,然后中斷所有空閑線程,然后執行tryTerminate()方法(tryTerminate這個方法很重要,會在后面分析),來嘗試終止線程池;
shutdownNow會將線程池狀態設置為STOP狀態,然后中斷所有線程(不管有沒有執行任務都設置為中斷狀態),然后執行tryTerminate()方法,來嘗試終止線程池;
- shutdown
流程圖如下:
這里的中斷只是發出中斷信號,而在獲取任務阻塞的線程會識別到對應的中斷信號,然后拋出異常,執行完方法退出
//嘗試停止線程池,此時不接受新的任務,但是會處理等待隊列的任務
public void shutdown() {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
//檢查關閉線程池的權限
checkShutdownAccess();
//把線程池狀態更新到SHUTDOWN
advanceRunState(SHUTDOWN);
//中斷閑置的Worker
interruptIdleWorkers();
// 鉤子方法,默認不處理。ScheduledThreadPoolExecutor會做一些處理
onShutdown();
} finally {
mainLock.unlock();
}
//嘗試結束線程池
tryTerminate();
}
//更新指定狀態
private void advanceRunState(int targetState) {
for (; ; ) {
int c = ctl.get();
//假設targetState=SHUTDOWN以下兩種情況會結束自旋
//1.當前線程池狀態不是運行狀態
//2.當前線程狀態是運行狀態,但是cas比較更新當前線程數和線程池狀態為SHUTDOWN成功
if (runStateAtLeast(c, targetState) ||
ctl.compareAndSet(c, ctlOf(targetState, workerCountOf(c))))
break;
}
}
中斷閑置線程
//傳true,中斷一個worker,false則中斷全部閑置的worker,
private void interruptIdleWorkers() {
interruptIdleWorkers(false);
}
//傳入了參數false,表示要中斷所有的正在運行的閑置Worker,如果為true表示只打斷一個閑置Worker
private void interruptIdleWorkers(boolean onlyOne) {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
for (Worker w : workers) {
Thread t = w.thread;
// Worker中的線程沒有被打斷并且Worker可以獲取鎖,這里Worker能獲取鎖說明Worker是個閑置Worker,
// 在阻塞隊列里拿數據一直被阻塞,沒有數據進來。如果沒有獲取到Worker鎖,說明Worker還在執行任務,
// 不進行中斷(shutdown方法不會中斷正在執行的任務)
if (!t.isInterrupted() && w.tryLock()) {
try {
t.interrupt();
} catch (SecurityException ignore) {
} finally {
w.unlock();
}
}
if (onlyOne)
break;
}
} finally {
mainLock.unlock();
}
}
- shutdownNow
流程圖如下:
這里的中斷只是發出中斷信號,而在獲取任務阻塞的線程會識別到對應的中斷信號,然后拋出異常,執行完方法退出
//嘗試停止線程池,此時不接受新的任務,也不會處理等待隊列的任務
public List<Runnable> shutdownNow() {
List<Runnable> tasks;
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
//檢查關閉線程池的權限
checkShutdownAccess();
//把線程池狀態更新到STOP
advanceRunState(STOP);
//中斷所有Worker,不管是否閑置
interruptWorkers();
//清空等待隊列的任務,并且返回清除成功的這些任務的集合
tasks = drainQueue();
} finally {
mainLock.unlock();
}
//嘗試結束線程池
tryTerminate();
//返回清除成功的這些任務的集合
return tasks;
}
更新指定狀態
//更新指定狀態
private void advanceRunState(int targetState) {
for (; ; ) {
int c = ctl.get();
//假設targetState=SHUTDOWN以下兩種情況會結束自旋
//1.當前線程池狀態不是運行狀態
//2.當前線程狀態是運行狀態,但是cas比較更新當前線程數和線程池狀態為SHUTDOWN成功
if (runStateAtLeast(c, targetState) ||
ctl.compareAndSet(c, ctlOf(targetState, workerCountOf(c))))
break;
}
}
停止所有的啟動線程,不管是否閑置
//停止所有的啟動線程,不管是否閑置
private void interruptWorkers() {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
for (Worker w : workers)
w.interruptIfStarted();
} finally {
mainLock.unlock();
}
}
傳入了參數false,表示要中斷所有的正在運行的閑置Worker,如果為true表示只打斷一個閑置Worker
void interruptIfStarted() {
Thread t;
if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) {
try {
t.interrupt();
} catch (SecurityException ignore) {
}
}
}
對阻塞隊列的任務進行清空
//對阻塞隊列的任務進行清空
private List<Runnable> drainQueue() {
BlockingQueue<Runnable> q = workQueue;
ArrayList<Runnable> taskList = new ArrayList<Runnable>();
// 該方法會將阻塞隊列中的所有項添加到 taskList 中
// 然后清空任務隊列,該方法是線程安全的
q.drainTo(taskList);
//隊列還沒清空?
if (!q.isEmpty()) {
// 將 List 轉換為 數組,傳入的 Runnable[0] 用來說明是轉為 Runnable 數組
//也就是q轉成了Runnable[],q有幾個元素,Runnable[]有幾個元素
for (Runnable r : q.toArray(new Runnable[0])) {
//從隊列里移除指定元素
if (q.remove(r))
//移除成功,添加到任務集合
taskList.add(r);
}
}
//返回清空成功的任務集合
return taskList;
}
關于finalize方法說明:
垃圾回收時,如果判斷對象不可達,且覆蓋了finalize方法,則會將對象放入到F-Queue隊列 ,有一個名為”Finalizer”的守護線程執行finalize方法,它的優先級為8,做最后的清理工作,執行finalize方法完畢后,GC會再次判斷該對象是否可達,若不可達,則進行回收,否則,對象復活
注意:網上很多人說 ,Finalizer線程的優先級低,個人認為這是不對的,Finalizer線程在jdk1.8的優先級是8,比我們創建線程默認優先級5要高,之前其它版本的jdk我記得導出的線程棧信息里面優先級是5,忘記是哪個版本的jdk了,即使是5優先級也不比自建的線程默認優先級低,總之我沒見過優先級低于5的Finalizer線程;
這個線程會不停的循環等待java.lang.ref.Finalizer.ReferenceQueue中的新增對象。一旦Finalizer線程發現隊列中出現了新的對象,它會彈出該對象,調用它的finalize()方法,將該引用從Finalizer類中移除,因此下次GC再執行的時候,這個Finalizer實例以及它引用的那個對象就可以回垃圾回收掉了。
大多數時候,Finalizer線程能夠趕在下次GC帶來更多的Finalizer對象前清空這個隊列,但是當它的處理速度沒法趕上新對象創建的速度,對象創建的速度要比Finalizer線程調用finalize()結束它們的速度要快,這導致最后堆中所有可用的空間都被耗盡了;
當我們大量線程頻繁創建重寫了finalizer()方法的對象的情況下,高并發情況下,它可能會導致你內存的溢出;雖然Finalizer線程優先級高,但是畢竟它只有一個線程;最典型的例子就是數據庫連接池,proxool,對要釋放資源的操作加了鎖,并在finalized方法中調用該加鎖方法,在高并發情況下,鎖競爭嚴重,finalized競爭到鎖的幾率減少,finalized無法立即釋放資源,越來越多的對象finalized()方法無法被執行,資源無法被回收,最終導致導致oom;所以覆蓋finalized方法,執行一定要快,不能有鎖競爭的操作,否則在高并發下死的很慘;
嘗試終止線程池tryTerminate
該方法會在很多地方調用,如添加worker線程失敗的addWorkerFailed()方法,worker線程跳出執行任務的while 循環退出時的processWorkerExit()方法,關閉線程池的shutdown()和shutdownNow()方法,從任務隊列移除任務的remove()方法;
該方法的作用是檢測當前線程池的狀態是否可以將線程池終止,如果可以終止則嘗試著去終止線程,否則直接返回;
STOP->TIDYING 與SHUTDOWN->TIDYING狀態的轉換,就是在該方法中實現的,最終執行terminated()方法后會把線程狀態設置為TERMINATED的狀態;
嘗試終止線程池執行過程;
- 重點內容先判斷線程池的狀態是否允許被終止
以下狀態不可被終止:
1.如果線程池的狀態是RUNNING(不可終止)
或者是TIDYING(該狀態一定執行過了tryTerminate方法,正在執行或即將執行terminated()方法,所以不需要重復執行),
或者是TERMINATED(該狀態已經執行完成terminated()鉤子方法,已經是被終止狀態了),
以上三種狀態直接返回。
2.如果線程池狀態是SHUTDOWN,而且任務隊列不是空的(該狀態需要繼續處理任務隊列中的任務,不可被終止),也直接返回。
以下兩種狀態線程池可以被終止:
1.如果線程池狀態是SHUTDOWN,而且任務隊列是空的(shutdown狀態下,任務隊列為空,可以被終止),向下進行。
2.如果線程池狀態是STOP(該狀態下,不接收新任務,不執行任務隊列中的任務,并中斷正在執行中的線程,可以被終止),向下進行。
- 線程池狀態可以被終止,如果線程池中仍然有線程,則嘗試中斷線程池中的線程
則嘗試中斷一個線程然后返回,被中斷的這個線程執行完成退出后,又會調用tryTerminate()方法,中斷其它線程,直到線程池中的線程數為0,則繼續往下執行; - 如果線程池中的線程為0,則將狀態設置為TIDYING,設置成功后執行 terminated()方法,最后將線程狀態設置為TERMINATED
源碼如下:
//嘗試關閉線程池
//嘗試關閉線程池
final void tryTerminate() {
for (; ; ) {
int c = ctl.get();
// 滿足3個條件中的任意一個,不終止線程池
// 1. 線程池還在運行,不能終止
// 2. 線程池處于TIDYING或TERMINATED狀態,說明已經在關閉了,不允許繼續處理
// 3. 線程池處于SHUTDOWN狀態并且阻塞隊列不為空,這時候還需要處理阻塞隊列的任務,不能終止線程池
if (isRunning(c) ||
runStateAtLeast(c, TIDYING) ||
(runStateOf(c) == SHUTDOWN && !workQueue.isEmpty()))
return;
// 走到這一步說明線程池已經不在運行,阻塞隊列已經沒有任務,但是還要回收正在工作的Worker
//還剩下 SHUTDOWN&&workQueue.isEmpty 、STOP這兩種狀態
if (workerCountOf(c) != 0) {
// 由于線程池不運行了,調用了線程池的關閉方法,在解釋線程池的關閉原理的時候會說道這個方法
// 中斷閑置Worker,直到回收全部的Worker。這里沒有那么暴力,只中斷一個閑置線程,
//發出中斷信號,中斷阻塞在獲取任務的線程,然后還是會調用tryTerminate方法,如果還有閑置線程,那么繼續中斷
interruptIdleWorkers(ONLY_ONE);
return;
}
// 走到這里說明worker已經全部回收了,并且線程池已經不在運行,阻塞隊列已經沒有任務。可以準備結束線程池了
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
// cas操作,將線程池狀態改成TIDYING
if (ctl.compareAndSet(c, ctlOf(TIDYING, 0))) {
try {
//鉤子方法,沒有任何實現
terminated();
} finally {
//terminated方法調用完畢之后,狀態變為TERMINATED
ctl.set(ctlOf(TERMINATED, 0));
termination.signalAll();
}
return;
}
} finally {
mainLock.unlock();
}
// else retry on failed CAS
}
}
傳入了參數false,表示要中斷所有的正在運行的閑置Worker,如果為true表示只打斷一個閑置Worker
//傳入了參數false,表示要中斷所有的正在運行的閑置Worker,如果為true表示只打斷一個閑置Worker
private void interruptIdleWorkers(boolean onlyOne) {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
for (Worker w : workers) {
Thread t = w.thread;
// Worker中的線程沒有被打斷并且Worker可以獲取鎖,這里Worker能獲取鎖說明Worker是個閑置Worker,
// 在阻塞隊列里拿數據一直被阻塞,沒有數據進來。如果沒有獲取到Worker鎖,說明Worker還在執行任務,
// 不進行中斷(shutdown方法不會中斷正在執行的任務)
if (!t.isInterrupted() && w.tryLock()) {
try {
t.interrupt();
} catch (SecurityException ignore) {
} finally {
w.unlock();
}
}
if (onlyOne)
break;
}
} finally {
mainLock.unlock();
}
}
總結幾個問題:
- 說說線程池中的線程創建時機?
1. 線程池處于RUNNING和(SHUTDOWN+等待隊列不為空)狀態,并且當前線程數量小于核心數,
那么提交任務的時候創建一個新的線程,并由這個線程執行這個任務。
2. 如果當前線程數已經達到 corePoolSize,那么將提交的任務添加到隊列中,
等待線程池中的線程去隊列中取任務。
3. 如果隊列已滿,那么創建新的線程來執行任務,需要保證池中的線程數不會超過 maximumPoolSize,
如果此時線程數超過了 maximumPoolSize,那么執行拒絕策略。
- 注意:如果將隊列設置為無界隊列,那么線程數達到 corePoolSize 后,其實線程數就不會再增長了。因為后面的任務直接往隊列塞就行了,此時 maximumPoolSize 參數就沒有什么意義。
- 任務執行過程中發生異常怎么處理?
如果某個任務執行出現異常,那么執行任務的線程會被關閉,而不是繼續接收其他任務。
然后經過一系列確認,比如線程池的狀態是否啟動、有沒有未完成的任務不夠線程執行等等,
然后會啟動一個新的線程來代替它。
- 什么時候會執行拒絕策略?
1、workers 的數量達到了 corePoolSize(任務此時需要進入任務隊列),任務入隊成功,
與此同時線程池被關閉了并且剛剛加入隊列的任務移除成功,那么執行拒絕策略。
2、workers 的數量大于等于 corePoolSize,將任務加入到任務隊列,可是隊列滿了,任務入隊失敗,
那么準備開啟新的線程,可是線程數已經達到 maximumPoolSize,那么執行拒絕策略。
線程池看了幾天,一開始看得有點蒙蔽,慢慢才看懂,喜歡給個贊!!!!!