java.util.concurrent包系列文章
JUC—ThreadLocal源碼解析(JDK13)
JUC—ThreadPoolExecutor線程池源碼解析(JDK13)
JUC—各種鎖(JDK13)
JUC—原子類Atomic*.java源碼解析(JDK13)
JUC—CAS源碼解析(JDK13)
JUC—ConcurrentHashMap源碼解析(JDK13)
JUC—CopyOnWriteArrayList源碼解析(JDK13)
JUC—併發隊列源碼解析(JDK13)
JUC—多線程下控制併發流程(JDK13)
JUC—AbstractQueuedSynchronizer解析(JDK13)
線程池的組成
- 線程池管理器 -> thread-pool
- 工作線程(線程池中在運行的線程)-> t0,t1,t2…t9
- 任務隊列-> blocking-queue
- 任務接口(task)-> 隊列中的task1,task2…
- Executor 頂層接口,只有一個 execute方法
void execute(Runnable command);
- ExecutorService 定義了一些管理線程池,判斷線程池狀態的方法
void shutdown();優雅的關閉線程池(開始拒絕任務,並等待正在執行的線程執行完)
List<Runnable> shutdownNow();立即停止線程池,中斷正在執行的任務
boolean isShutdown();返回線程池是否停止
<T> Future<T> submit(Runnable task, T result);提交新的任務到線程池
。。。
- ThreadPoolExecutor 真正的線程池類,Executors 的快捷創建線程池的方法內部就是new ThreadPoolExecutor()創建的線程池。
比如 newFixedThreadPool
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
- Executors 就是一個線程池工具類,提供了一些簡單創建線程池的方法。一般不用,它的缺點後面會介紹。
ThreadPoolExecutor源碼解讀
void execute(Runnable command)
public void execute(Runnable command) {
// null就直接拋異常
if (command == null)
throw new NullPointerException();
int c = ctl.get();
// 首先檢查是否小於核心線程數
if (workerCountOf(c) < corePoolSize) {
// 如果當前工作的線程數小於核心線程數,則增加一個線程來運行任務,command就是提交進來的任務
// 第二個參數的意思:true表示在增加線程時判斷是否小於核心線程數,false表示判斷是否小於最大線程數
if (addWorker(command, true))
return;
c = ctl.get();
}
// 如果線程數量大於核心線程數,先判斷線程是否是運行狀態,是的話就把任務放到工作隊列中
if (isRunning(c) && workQueue.offer(command)) {
// 再次檢查一下線程狀態,如果線程不是在運行,就把剛剛那個任務刪掉,並且決絕,因爲線程已經停止了
int recheck = ctl.get();
if (! isRunning(recheck) && remove(command))
reject(command);
// 如果發現當前運行的線程爲0了,則直接創建新線程執行任務
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
// 走到這裏說明,要麼線程數大於等於核心線程,要麼線程池停止了,要麼隊列也滿了
// 就直接創建新的線程執行任務(當然,這個數量要小於最大線程數,如果達到了最大線程數,則執行拒絕策略)
// addWorker()方法返回是否增加成功
else if (!addWorker(command, false))
reject(command);
}
以上就是線程池最核心的流程,後面會給出流程圖說明。
再來看看線程池是如何做到線程不停止,直接複用執行任務的。
boolean addWorker(Runnable firstTask, boolean core);
private boolean addWorker(Runnable firstTask, boolean core) {
retry:
// 這一部分是for死循環,通過CAS操作修改當前線程數
for (int c = ctl.get();;) {
// Check if queue empty only if necessary.
if (runStateAtLeast(c, SHUTDOWN)
&& (runStateAtLeast(c, STOP)
|| firstTask != null
|| workQueue.isEmpty()))
return false;
for (;;) {
if (workerCountOf(c)
>= ((core ? corePoolSize : maximumPoolSize) & COUNT_MASK))
return false;
if (compareAndIncrementWorkerCount(c))
break retry;
c = ctl.get(); // Re-read ctl
if (runStateAtLeast(c, SHUTDOWN))
continue retry;
// else CAS failed due to workerCount change; retry inner loop
}
}
boolean workerStarted = false;
boolean workerAdded = false;
Worker w = null;
try {
// 最重要的就是這裏,把提交的任務封裝成了Worker對象
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 c = ctl.get();
if (isRunning(c) ||
(runStateLessThan(c, STOP) && firstTask == null)) {
if (t.getState() != Thread.State.NEW)
throw new IllegalThreadStateException();
// workers是一個HashSet<Worker> ,接下來看runWorker()方法怎麼複用執行任務的
workers.add(w);
workerAdded = true;
int s = workers.size();
if (s > largestPoolSize)
largestPoolSize = s;
}
} finally {
mainLock.unlock();
}
if (workerAdded) {
t.start();
workerStarted = true;
}
}
} finally {
if (! workerStarted)
addWorkerFailed(w);
}
return workerStarted;
}
void runWorker(Worker w)
final void runWorker(Worker w) {
Thread wt = Thread.currentThread();
Runnable task = w.firstTask;
w.firstTask = null;
w.unlock(); // allow interrupts
boolean completedAbruptly = true;
try {
// 複用的原理:while循環,通過getTask()不斷的去隊列裏取任務來執行
while (task != null || (task = getTask()) != null) {
w.lock();
// If pool is stopping, ensure thread is interrupted;
// if not, ensure thread is not interrupted. This
// requires a recheck in second case to deal with
// shutdownNow race while clearing interrupt
if ((runStateAtLeast(ctl.get(), STOP) ||
(Thread.interrupted() &&
runStateAtLeast(ctl.get(), STOP))) &&
!wt.isInterrupted())
wt.interrupt();
try {
beforeExecute(wt, task);
try {
// 最關鍵的這裏,就是執行了我們提交的每一個線程任務的run方法來執行任務的。只不過Worker 包裝了一下
task.run();
afterExecute(task, null);
} catch (Throwable ex) {
afterExecute(task, ex);
throw ex;
}
} finally {
task = null;
w.completedTasks++;
w.unlock();
}
}
completedAbruptly = false;
} finally {
processWorkerExit(w, completedAbruptly);
}
}
最後看看
Runnable getTask()
private Runnable getTask() {
boolean timedOut = false; // Did the last poll() time out?
for (;;) {
int c = ctl.get();
// Check if queue empty only if necessary.
if (runStateAtLeast(c, SHUTDOWN)
&& (runStateAtLeast(c, STOP) || workQueue.isEmpty())) {
decrementWorkerCount();
return null;
}
int wc = workerCountOf(c);
// Are workers subject to culling?
boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
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;
}
}
}
源碼就貼到這裏,線程池的整個流程應該比較清晰了。下面再補充一下。
ThreadPoolExecutor參數
- corePoolSize:核心線程數,線程池完成初始化以後,線程池中並沒有任何線程,線程池會等待有任務到來時,再創建線程去執行任務。
- maxPoolSize:最大線程數
- keepAliveTime:空閒線程存活時間。如果線程池當前的線程數多於corePoolSize,多於的線程空閒時間超過keepAliveTime,它們就會終止
- ThreadFactory:新的線程由ThreadFactory創建,默認使用Executors.defaultThreadFactory(),自己制定ThreadFactory,可以設置線程名,線程組,優先級,是否是守護線程等。
- workQueue:工作隊列
- 直接交接:SynchronousQueue,不存儲任務,任務進來直接創建線程處理,可以無限創建線程
- 無界隊列:LinkedBlockingQueue,如果處理的速度跟不上任務提交的速度,那麼任務會越來越多。有OOM風險。
- 有界隊列:ArrayBlockingQueue,可以設置隊列大小
添加線程的規則流程
新任務來的時候,即使其他工作線程處於空閒,也會創建一個新線程來運行新任務。 如果線程數>=corePoolSize但少於maxPoolSize。則將任務放入隊列中。如果隊列已,並且線程數小於maxPoolSize,則創建一個新線程來運行任務。如果隊列已滿並且線程數>=maxPoolSize,則拒絕該任務。
JDK默認提供的線程池(Executors工具類中)
- newFixedThredPool:無界隊列,容易造成佔用大量內存導致OOM。
- newSingleThreadExecutor:單線程,無界隊列,請求堆積的時候也會導致OOM。
- newCachedThreadPool:corePoolSize設置爲0,maxPoolSize設置爲Integer.MAX_VALUE使用的SynchronousQueue。來了任務直接就給線程執行了。請求堆積的時候也會導致OOM。用完線程就全部回收銷燬。
- newScheduledThreadPool:延遲隊列DelayedWorkQueue,支持定時以及週期性執行任務。使用於要重複運行的任務。類似於定時任務。
- workStealingPool:1.8加入。處理子任務。子任務放到每個線程獨有的任務隊列中,某個線程執行完了,可以竊取別的線程中子任務來執行。遞歸+子任務的場景使用
線程池的狀態
- RUNING:接受新任務並處理排隊任務
- SHUTDOWN:不接受新任務,但處理排隊任務
- STOP:不接受新任務,也不處理排隊任務,並中斷正在執行的任務
- TIDING:所有任務都已終止,workCount爲零時,線程會轉換到TIDING狀態。並將運行terminate()鉤子方法。
- TERMINATED:terminate()運行完成
停止線程池
- shutdown:優雅的關閉線程池。執行完隊列中的存量任務,拒絕新任務加入。
- isShutdown:線程是否進入停止狀態(boolean)。
- isTerminated:返回線程是否真的停止,指任務全都執行完畢。
- awaitTermination:檢測線程是否在指定的時間內停止了。會阻塞等待指定時間。
- shutdownNow:立刻關閉線程池 。正在執行的線程會收到interruptedException異常。返回一個沒有執行的任務List。
線程池拒絕任務
- 當Executor關閉時,提交新任務會被拒絕。
- 當Executor線程滿和隊列也滿了會拒絕。
拒絕策略
- AbortPolicy:拋出異常
- DiscardPolicy:丟棄新提交的任務
- DiscardOldestPolicy:丟棄最老的任務
- CallerRunsPolicy:誰提交的任務交給誰去執行(讓提交任務的線程去執行)
線程池的線程設置爲多少比較合適
- CPU密集型(加密、計算等):最佳線程數爲CPU核心的1-2倍
- 耗時IO型(讀寫數據庫、文件、網絡讀寫):最佳線程數爲CPU核心的5-10倍
- 線程數=CPU核心數*(1+平均等待時間/平均工作時間)
- 我的公衆號:Coding摳腚
- 一個被電焊耽誤的Java程序員。偶爾發發自己最近學到的乾貨。學習路線,經驗,技術分享。技術問題交流探討。