- Posted by 微博@Yangsc_o
- 原創文章,版權聲明:自由轉載-非商用-非衍生-保持署名 | Creative Commons BY-NC-ND 3.0
摘要
本文主要回顧java的JDK中的多線程的常見用法和深入理解線程池;
java中的線程
- 創建線程的3種方式
- 通過實現 Runnable 接口來創建線程
- 通過繼承Thread來創建線程
- 通過 Callable 和 Future 創建線程
- 開啓線程的方法
- public void start() 使該線程開始執行;Java 虛擬機調用該線程的 run 方法。
- public void run() 如果該線程是使用獨立的 Runnable 運行對象構造的,則調用該 Runnable 對象的 run 方法;否則,該方法不執行任何操作並返回。
- 線程的狀態
public enum State {
NEW,
RUNNABLE,
BLOCKED,
WAITING,
TIMED_WAITING,
TERMINATED;
}
java中的線程池
線程池技術
線程池(Thread Pool)是一種基於池化思想管理線程的工具,經常出現在多線程服務器中,如MySQL等,追根溯源-編程語言&性能優化 這篇文檔也指出優化性能的一種手段就是池化技術,今天就稍微展開一下,池話技術;
- 池化的表現形式
池化技術在計算機領域中的表現爲:統一管理IT資源,包括服務器、存儲、和網絡資源等等。通過共享資源,使用戶在低投入中獲益。除去線程池,還有其他比較典型的幾種使用策略包括:
- 內存池(Memory Pooling):預先申請內存,提升申請內存速度,減少內存碎片。
- 連接池(Connection Pooling):預先申請數據庫、redis連接,提升申請連接的速度,降低系統的開銷。
- 實例池(Object Pooling):循環使用對象,減少資源在初始化和釋放時的昂貴損耗。
- 線程池解決的問題
解決的核心問題就是資源管理問題。在併發環境下,系統不能夠確定在任意時刻中,有多少任務需要執行,有多少資源需要投入。
這種不確定性將帶來以下若干問題
- 頻繁申請/銷燬資源和調度資源,將帶來額外的消耗,可能會非常巨大。
- 對資源無限申請缺少抑制手段,易引發系統資源耗盡的風險。
- 系統無法合理管理內部的資源分佈,會降低系統的穩定性。
反之就是解決的問題
- 降低資源消耗:通過池化技術重複利用已創建的線程,降低線程創建和銷燬造成的損耗。
- 提高響應速度:任務到達時,無需等待線程創建即可立即執行。
- 提高線程的可管理性:線程是稀缺資源,如果無限制創建,不僅會消耗系統資源,還會因爲線程的不合理分佈導致資源調度失衡,降低系統的穩定性。使用線程池可以進行統一的分配、調優和監控。
線程池的實現原理
簡述
Java中的線程池核心實現類是ThreadPoolExecutor,JDK 1.8的源碼重的ThreadPoolExecutor的UML類圖,瞭解下ThreadPoolExecutor的繼承關係。
- Executor
void execute(Runnable command);
只有一個execute方法,只需提供Runnable對象
- ExecutorService
ExecutorService接口繼承了Executor接口,並聲明瞭一些方法:submit、invokeAll、invokeAny以及shutDown等;
- AbstractExecutorService
抽象類AbstractExecutorService實現了ExecutorService接口,基本實現了ExecutorService中聲明的所有方法;
- ThreadPoolExecutor
ThreadPoolExecutor繼承了類AbstractExecutorService,execute()、submit()、shutdown()、shutdownNow()
ThreadPoolExecutor是如何運行的?
ThreadPoolExecutor是如何運行,如何同時維護線程和執行任務的呢?
線程池在內部實際上構建了一個生產者消費者模型,將線程和任務兩者解耦,並不直接關聯,從而良好的緩衝任務,複用線程。線程池的運行主要分成兩部分:任務管理、線程管理。任務管理部分充當生產者的角色,當任務提交後,線程池會判斷該任務後續的流轉,策略如下:
(1)直接申請線程執行該任務;
(2)緩衝到隊列中等待線程執行;
(3)拒絕該任務。
線程管理部分是消費者,它們被統一維護在線程池內,根據任務請求進行線程的分配,當線程執行完任務後則會繼續獲取新的任務去執行,最終當線程獲取不到任務的時候,線程就會被回收。
線程池運行的狀態和線程數量
- 線程池運行的狀態和線程數量
狀態和線程數量是伴隨着線程池的運行,由內部來維護。線程池內部使用一個變量維護兩個值:運行狀態(runState)和線程數量 (workerCount)。在具體實現中,線程池將運行狀態(runState)、線程數量 (workerCount)兩個關鍵參數的維護放在了一起
英文描述:The main pool control state, ctl, is an atomic integer packing two conceptual fields workerCount, indicating the effective number of threads runState, indicating whether running, shutting down etc
--- 線程池運行的狀態和數量---
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
// Packing and unpacking ctl
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; }
---狀態值---
// runState is stored in the high-order bits
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;
- 狀態切換
任務執行機制
任務調度是線程池的主要入口,當用戶提交了一個任務,接下來這個任務將如何執行都是由這個階段決定的。任務的調度都是由execute方法完成的,這部分完成的工作是:檢查現在線程池的運行狀態、運行線程數、運行策略,決定接下來執行的流程,是直接申請線程執行,或是緩衝到隊列中執行,亦或是直接拒絕該任務。其執行過程如下:
- 首先檢測線程池運行狀態,如果不是RUNNING,則直接拒絕,線程池要保證在RUNNING的狀態下執行任務。
- 如果workerCount < corePoolSize,則創建並啓動一個線程來執行新提交的任務。
- 如果workerCount >= corePoolSize,且線程池內的阻塞隊列未滿,則將任務添加到該阻塞隊列中。
- 如果workerCount >= corePoolSize && workerCount < maximumPoolSize,且線程池內的阻塞隊列已滿,則創建並啓動一個線程來執行新提交的任務。
- 如果workerCount >= maximumPoolSize,並且線程池內的阻塞隊列已滿, 則根據拒絕策略來處理該任務, 默認的處理方式是直接拋異常。
- JDK源碼如下:
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
int c = ctl.get();
if (workerCountOf(c) < corePoolSize) {
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);
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
else if (!addWorker(command, false))
reject(command);
}
隊列緩存
任務緩衝模塊是線程池能夠管理任務的核心部分。通過源碼可以看到緩存使用的隊列是BlockingQueue;
private final BlockingQueue workQueue;
- 各種隊列的特點
- 拒絕策略
Worker線程管理
Worker線程
private final class Worker extends AbstractQueuedSynchronizer implements Runnable {
/** Thread this worker is running in. Null if factory fails. */
final Thread thread;
/** Initial task to run. Possibly null. */
Runnable firstTask;
/** Per-thread task counter */
volatile long completedTasks;
...
...
}
Worker這個工作線程,實現了Runnable接口,並持有一個線程thread,一個初始化的任務firstTask。thread是在調用構造方法時通過ThreadFactory來創建的線程,可以用來執行任務;firstTask用它來保存傳入的第一個任務,這個任務可以有也可以爲null。如果這個值是非空的,那麼線程就會在啓動初期立即執行這個任務,也就對應核心線程創建時的情況;如果這個值是null,那麼就需要創建一個線程去執行任務列表(workQueue)中的任務,也就是非核心線程的創建。
#### 線程的生命週期
線程池需要管理線程的生命週期,需要在線程長時間不運行的時候進行回收。線程池使用一張Hash表去持有線程的引用,這樣可以通過添加引用、移除引用這樣的操作來控制線程的生命週期。這個時候重要的就是如何判斷線程是否在運行。
/**
* Set containing all worker threads in pool. Accessed only when
* holding mainLock.
*/
private final HashSet<Worker> workers = new HashSet<Worker>();
Worker是通過繼承AQS,使用AQS來實現獨佔鎖這個功能。關於AQS,改天專門寫一篇博客;
- Worker線程增加
通過閱讀源碼可以看到線程池是通過addWorker添加一個任務,添加有三種策略。
- addWorker方法有兩個參數:firstTask、core。
- firstTask參數用於指定新增的線程執行的第一個任務,該參數可以爲空;
- core參數爲true表示在新增線程時會判斷當前活動線程數是否少於corePoolSize,false表示新增線程前需要判斷當前活動線程數是否少於maximumPoolSize
private boolean addWorker(Runnable firstTask, boolean core) {
...
}
添加的策略
/*
* Proceed in 3 steps:
*
* 1. If fewer than corePoolSize threads are running, try to
* start a new thread with the given command as its first
* task. The call to addWorker atomically checks runState and
* workerCount, and so prevents false alarms that would add
* threads when it shouldn't, by returning false.
*
* 2. If a task can be successfully queued, then we still need
* to double-check whether we should have added a thread
* (because existing ones died since last checking) or that
* the pool shut down since entry into this method. So we
* recheck state and if necessary roll back the enqueuing if
* stopped, or start a new thread if there are none.
*
* 3. If we cannot queue task, then we try to add a new
* thread. If it fails, we know we are shut down or saturated
* and so reject the task.
*/
添加時的檢查
/**
* Checks if a new worker can be added with respect to current
* pool state and the given bound (either core or maximum). If so,
* the worker count is adjusted accordingly, and, if possible, a
* new worker is created and started, running firstTask as its
* first task. This method returns false if the pool is stopped or
* eligible to shut down. It also returns false if the thread
* factory fails to create a thread when asked. If the thread
* creation fails, either due to the thread factory returning
* null, or due to an exception (typically OutOfMemoryError in
* Thread.start()), we roll back cleanly.
*/
- Worker線程執行任務
在Worker類中的run方法調用了runWorker方法來執行任務,runWorker方法的執行過程如下:
- while循環不斷地通過getTask()方法獲取任務。
- getTask()方法從阻塞隊列中取任務。
- 如果線程池正在停止,那麼要保證當前線程是中斷狀態,否則要保證當前線程不是中斷狀態。
- 執行任務。
- 如果getTask結果爲null則跳出循環,執行processWorkerExit()方法,銷燬線程。
/** Delegates main run loop to outer runWorker */
public void run() {
runWorker(this);
}
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 (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);
Throwable thrown = null;
try {
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 {
processWorkerExit(w, completedAbruptly);
}
}
- Worker線程回收
線程池中線程的銷燬依賴JVM自動的回收,線程池做的工作是根據當前線程池的狀態維護一定數量的線程引用,防止這部分線程被JVM回收,當線程池決定哪些線程需要回收時,只需要將其引用消除即可。Worker被創建出來後,就會不斷地進行輪詢,然後獲取任務去執行,核心線程可以無限等待獲取任務,非核心線程要限時獲取任務。當Worker無法獲取到任務,也就是獲取的任務爲空時,循環會結束,Worker會主動消除自身在線程池內的引用,從源碼中可以看到,線程回收的工作是在processWorkerExit方法完成的。
final void runWorker(Worker w) {
try {
} finally {
processWorkerExit(w, completedAbruptly);
}
}
建線程池
- 創建線程池的四個方法
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 static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory) {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(),
threadFactory));
}
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
}
- newSingleThreadExecutor
newSingleThreadExecutor 創建一個單線程化的線程池,它只會用唯一的工作線程來執行任務,保證所有任務按照指定順序(FIFO, LIFO, 優先級)執行。
- newFixedThreadPool
創建一個定長線程池,可控制線程最大併發數,超出的線程會在隊列中等待。
- newCachedThreadPool
創建一個可緩存的線程池。如果線程池的大小超過了處理任務所需要的線程,線程的最大數量是Integer.MAX_VALUE;
- newScheduledThreadPool
創建一個定長線程池,支持定時及週期性任務執行。線程的最大數量是Integer.MAX_VALUE;
- 上面的四個最後都會調用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.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
- int corePoolSize: 核心線程池大小
- int maximumPoolSize: 最大核心線程池大小
- long keepAliveTime: 超時了沒有人調用就會釋放
- TimeUnit unit: 超時單位
- BlockingQueue < Runnable > workQueue: 阻塞隊列
- ThreadFactory threadFactory: 線程工廠
- RejectedExecutionHandler handler: 拒絕策略
就是BlockingQueue的四種拒絕策略
參考
深入理解Java線程池:ThreadPoolExecutor
Java 多線程編程
Java線程池實現原理及其在美團業務中的實踐
從ReentrantLock的實現看AQS的原理及應用