目錄
什麼是線程池
線程池是一種多線程處理形式,處理過程中將任務添加到隊列,然後在創建線程後自動啓動這些任務。線程池的核心邏輯是提前創建好若干個線程放在一個容器中。如果有任務需要被處理,則將任務直接分配給線程池中的線程來執行,任務處理完之後線程不會被立即銷燬,而是等待後續分配任務。同時通過線程池來重複管理線程還可以避免創建大量線程增加開銷。
使用線程池的優勢
1.降低創建線程和銷燬線程的性能開銷。
2.提高響應速度,當有新任務需要執行是不需要等待線程創建就可以立馬執行。
3.合理的設置線程池大小可以避免因爲線程數超過硬件資源瓶頸帶來的問題。
簡單使用
通過執行結果可以體現出線程池的特性
public class SelfThreadPool implements Runnable{
/**
* 伸縮性,可以動態調整線程數。空閒線程會在60S以後被回收
*/
ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
/**
* 固定線程數的線程池
*/
static ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);
/**
* 定時任務的線程池
*/
ExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(3);
/**
* 只有一個核心線程的線程池
*/
ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
/**
* fork/join 線程池
*/
ExecutorService workStealingPool = Executors.newWorkStealingPool();
@Override
public void run() {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Thread Name:"+Thread.currentThread().getName());
}
public static void main(String[] args) {
for (int i = 0; i < 100; i++) {
fixedThreadPool.execute(new SelfThreadPool());
}
fixedThreadPool.shutdown();
}
}
ThreadpoolExecutor
線程池的創建都是基於ThreadpoolExecutor來創建的。找到該類參數最多的構造方法。
corePoolSize : 核心線程數量
maximumPoolSize : 線程池所允許的最大線程數量
keepAliveTime : 超時時間,超出核心線程數量以外的線程的空餘存活時間
unit : 超時時間單位
workQueue : 阻塞隊列,保存執行任務的隊列
threadFactory : 線程工廠-創建線程的工廠對象
handler : 拒絕策略,當任務無法執行的時候的處理方式
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;
}
Java中提供的可創建的線程池的種類
調用Executors的工廠方法,可以創建不同種類的線程池
newFixedThreadPool:返回一個固定數量的線程池,線程數不變,當有一個任務提交的時候。若線程池空閒,則立即執行。若線程池不空閒,則會被暫緩在一個任務隊列中,等待有空閒的線程去執行。此線程池的核心線程數和最大線程數都是指定值。當線程池中的線程數達到核心線程數量之後,任務都會被放到阻塞隊列中。線程反覆從隊列中取任務執行
newSingleThreadExecutor:創建只有一個線程的線程池,若空閒則執行,若沒有空閒線程則暫緩在任務隊列中。由於只有一個線程,保證所有任務按照指定順序(FIFO,LIFO優先級)執行
newCachedThreadPool:返回一個可以根據實際情況調整線程個數的線程池,不限制最大線程數量,若有空閒的線程則執行任務,若無空閒的線程將會繼續創建線程。並且每一個線程超過空閒時間都會被回收。
newScheduledThreadPool:創建一個可以指定線程的數量的線程池,該線程還帶有延遲和週期性執行任務的功能,類似定時器。
workStealingPool :fork/join 線程池
線程池的原理
爲什麼線程池要比手動創建線程高效?
線程池初始化是沒有創建線程的,線程池的線程初始化與其它創建線程的方式一樣,但是在完成任務以後。線程池中線程不會立即自行銷燬,而是以掛起的狀態返回到線程池。當有應用程序再次向線程池發出請求時,線程池裏掛起的任務就會再度激活執行任務。這樣做節省了創建線程所造成的性能損耗,也可以讓多個任務反覆重用同一個線程,從而在應用程序生存週期內節約大量開銷。
線程池原理流程圖
源碼分析學習
基於入口,首先看execute方法。
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);
}
關於ctl
AtomicInteger是一個原子類,它的主要作用是用來保存線程的數量和線程的狀態。如下代碼,一個int的數值是32個bit位,高三位來保存運行狀態,低29位來保存線程數量。
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
// rs-> -1 左移 29位 wc-> 0 即 111|000 -》 111
private static int ctlOf(int rs, int wc) { return rs | wc; }
// 方法 ctlOf 參數 rs
// -1 左移 29位 -1 的二進制是32個1
// -1 左移 29位 結果爲 111
private static final int RUNNING = -1 << COUNT_BITS;
// 32-3 = 29
private static final int COUNT_BITS = Integer.SIZE - 3;
// 1 左移 29 位是 0000 1000 00000 00000 0000 0000 0000 0000
// 減1 表示 線程的最大容量
private static final int CAPACITY = (1 << COUNT_BITS) - 1;
addWorker
如果工作線程數小於核心線程數,調用該方法。即創建一個工作線程。該方法主要做了兩件事
1.採用CAS操作來將線程數+1
2.新建一個線程並啓用
總結:新建了一個 Worker 對象,該對象中維護了一個初始化任務-firstTask 何一個線程對象 final Thread thread 初始化Worker對象的時候完成對 firstTask 和 thread 的初始化。
private boolean addWorker(Runnable firstTask, boolean core) {
// 避免死循環
retry:
for (;;) {
int c = ctl.get();
// 獲得線程的運行狀態
int rs = runStateOf(c);
// 1.若是線程處於 shutdown 狀態,還要添加新任務,直接拒絕
// 2.
if (rs >= SHUTDOWN &&
! (rs == SHUTDOWN &&
firstTask == null &&
! workQueue.isEmpty()))
return false;
for (;;) {
// 獲得工作線程數
int wc = workerCountOf(c);
// 把 工作線程數 和 默認容量、核心線程數 進行比較
if (wc >= CAPACITY ||
wc >= (core ? corePoolSize : maximumPoolSize))
return false;
// CAS操作自增,線程數+1
if (compareAndIncrementWorkerCount(c))
break retry;
c = ctl.get(); // Re-read ctl
if (runStateOf(c) != rs)
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);
// 從 Worker 裏面取出線程
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);
int s = workers.size();
if (s > largestPoolSize)
largestPoolSize = s;
workerAdded = true;
}
} finally {
mainLock.unlock();
}
if (workerAdded) {
// 這裏線程 t 調用start方法 本質上是啓動線程執行了worker對象中run方法
t.start();
workerStarted = true;
}
}
} finally {
if (! workerStarted)
addWorkerFailed(w);
}
return workerStarted;
}
Worker類
通過分析源碼:addWorker方法構造了一個Worker。該類繼承了AQS,並實現了Runnable接口。繼承AQS的目的主要是爲了實現同步阻塞。
1.每一個Worker中包含一個firstTask,即初始化時首要被執行的任務。
2.最終執行任務的是runWorker()方法
總結:需要被執行的任務作爲參數傳遞給Worker對象,Worker對象初始化的時候會調用線程工廠方法創建線程,Worker對象創建成功會保存在Set集合中。Worker創建並添加成功之後會由Worker對象中新建的線程執行start()方法啓動線程。這裏start()方法啓動後,啓動對應的run方法是worker對象中的run方法。最終調用了runWorker方法。
相對應的,如果添加worker失敗,則會做失敗後的處理。主要是
1.如果Worker已經構造好了,則從Workers集合中移除這個worker
2.原子遞減核心線程數
3.嘗試結束線程池
private final class Worker
extends AbstractQueuedSynchronizer
implements Runnable
{
private static final long serialVersionUID = 6138294804551838833L;
/** Thread this worker is running in. Null if factory fails. */
/** 由線程工廠創建的工作線程--創建失敗則爲空 */
final Thread thread;
/** Initial task to run. Possibly null. */
/** 需要執行的 task */
Runnable firstTask;
/** Per-thread task counter */
/** 執行任務計數器 */
volatile long completedTasks;
/**
* Creates with given first task and thread from ThreadFactory.
* @param firstTask the first task (null if none)
*/
Worker(Runnable firstTask) {
setState(-1); // inhibit interrupts until runWorker
this.firstTask = firstTask;
this.thread = getThreadFactory().newThread(this);
}
/** Delegates main run loop to outer runWorker */
public void run() {
runWorker(this);
}
// Lock methods
//
// The value 0 represents the unlocked state.
// The value 1 represents the locked state.
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) {
}
}
}
}
runWorker
該方法是線程池執行任務的真正邏輯
1.task不爲空,則開始執行任務。
2.如果task爲空,則通過getTask()從任務隊列中去獲取任務。
3.執行完畢後,通過while循環繼續嘗試獲取任務並執行。getTask()爲空,則整個runWorker()方法執行完畢。
final void runWorker(Worker w) {
Thread wt = Thread.currentThread();
Runnable task = w.firstTask;
w.firstTask = null;
// 表示當前work線程允許被中斷 Worker初始化時默認的 state = -1
// 通過調用 Worker 類的 tryRelease() 方法,將 state 設置爲 0
// 當獲取鎖的時候 state 狀態設置爲 1
// interruptIfStarted() 中只有 state >= 0 才允許被中斷
w.unlock(); // allow interrupts
boolean completedAbruptly = true;
try {
// 實現線程複用
while (task != null || (task = getTask()) != null) {
// 上鎖 爲了在 shutdown() 時不終止正在運行的 worker
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 {
// 執行任務中的 run 方法
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
// 需要再通過 getTask()獲取任務 記錄該 Worker 完成任務數量 + 解鎖
task = null;
w.completedTasks++;
w.unlock();
}
}
completedAbruptly = false;
} finally {
// 將入參 worker 從數組 workers 裏刪除掉
processWorkerExit(w, completedAbruptly);
}
}
getTask方法
worker線程會從阻塞隊列中獲取需要執行的任務。
線程池中的線程是如何被幹掉的?
當線程從工作隊列中獲取任務的時候,有默認的超時限制,如果線程在keepAliveTime的時間內獲取不到任務,那麼就會認爲該條線程處於空閒狀態,可以進行銷燬。
總結:在執行execute方法時,如果當前線程池的線程數量超過了corePoolSize且小於maximumPoolSize。並且workQueue已滿時,則可以增加工作線程。但是如果超時沒有獲取到任務,說明隊列已經爲空了,說明線程池不需要超過核心線程數量的線程去執行任務了,可以把多餘的線程進行銷燬了,保持線程數量在corePoolSize即可。
private Runnable getTask() {
boolean timedOut = false; // Did the last poll() time out?
for (;;) {
int c = ctl.get();
int rs = runStateOf(c);
// Check if queue empty only if necessary.
// 做了兩個判斷 一:線程池狀態爲 shutdown 且 workQueue 爲空
// (反映了shutdown狀態的線程池還是要執行 workQueue 中剩餘的任務的)
// 二:線程池狀態爲 stop
if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
decrementWorkerCount();
return null;
}
int wc = workerCountOf(c);
// Are workers subject to culling?
// 判斷是否需要進行超時控制
// allowCoreThreadTimeOut 默認爲 false 核心線程不允許進行超時控制
// 當 當前線程池中的數量 大於 核心線程數量
// 對於超過核心線程數量的線程,需要進行超時控制
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;
}
}
}
當runWorker方法中while循環執行完畢以後,在finally中調用processWorkerExit來銷燬工作線程。
execute方法-addWorker之後
首先判斷線程池是否處於運行狀態並且任務隊列未滿,則將任務添加到隊列中。
如果核心池滿了,隊列也滿了,這個時候可以創建非核心線程。如果非核心線程數也達到了最大線程數大小,拒絕任務,執行拒絕策略。
// 判斷線程池是否處於運行狀態,並且任務隊列沒有滿
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);
關於拒絕策略
1.AbortPolicy 直接拋出異常,默認策略。
2.CallerRunsPolicy 用調用者所在的線程來執行任務
3.DiscardOldestPolicy 丟棄阻塞隊列中靠最前的任務,並執行當前任務
4.DiscardPolicy 直接丟棄任務
也可以根據場景實現RejectedExecutionHandler接口,自定義飽和策略,比如記錄日誌或持久化存儲不能處理的任務。
線程池相關注意事項
阿里開發手冊:線程池的構建不允許使用Executors去創建,而是要通過ThreadPoolExecutor的方式。
原因:使用Executors使得用戶不需要關心線程池的參數配置。會導致一些問題,比如我們使用newFixedThreadPool或者singleThreadPool 允許的隊列長度爲Integer.MAX_VALUE。使用不當會有OOM的風險。而使用ThreadPoolExecutor來構建線程池的話,我們勢必要了解線程池構造中每個參數的含義,使得開發者在配置參數的時候能夠更加謹慎。
如何合理配置線程池的大小
1.首先要分析線程池執行任務的特性:CPU密集型還是IO密集型
2.每個任務執行的平均時長大概是多少,這個任務的執行時長可能還和任務處理邏輯是否涉及網絡傳輸以及底層系統資源依賴有關係。
CPU密集型:CPU一直運行,利用率很高。線程數的配置應該根據CPU核心數來決定。CPU核心數=最大同時執行線程數。最大線程數=CPU核心數+1
IO密集型:主要進行IO操作,執行IO的時間較長。CPU處於空閒狀態,導致CPU利用率不高,這種情況下可以增加線程池的大小。一般需要結合線程的等待時長做判斷。等待時間越長,線程數相對越多。一般可以設置爲CPU核心數的兩倍。最佳數目可以依賴一個公式計算得出。
設定線程池最佳數目 = ((線程池設定的線程等待時間+CPU空閒時間)/ CPU空閒時間)*CPU數目。
相關細節
1.線程池初始化
默認情況下,線程池創建之後,線程池中是沒有線程的,需要提交任務之後纔會創建線程。實際使用的時候可以通過prestartCoreThread():初始化一個核心線程;prestartAllCoreThreads():初始化所有核心線程。
2.線程池的關閉
ThreadPoolExecutor提供了兩個方法,用於線程池的關閉,分別是shutdown()和shutdownNow().
shutdown():不會立即終止線程池,而是要等所有任務緩存隊列中的任務都執行完才終止,但是不會接受新的任務。
shutdownNow():立即終止線程池,並嘗試打斷正在執行的任務,並且清空任務緩存隊列,返回尚未執行的任務。
3.任務隊列
任務緩存隊列,即workQueue,用來存放等待執行的任務。其類型爲BlockingQueue
ArrayBlockingQueue :基於數組的先進先出隊列,此隊列創建時必須指定大小。
LinkedBlockingQueue :基於鏈表的先進先出隊列,如果創建的時候沒有指定此隊列的大小,默認Integer.MAX_VAUE
SynchronousQueue : 特殊隊列,不會保存提交的任務,而是直接創建一個線程執行新來的任務。
4.線程池的監控
線程池提供了相應的擴展方法,通過重寫線程池的beforeExecute afterExecute shutdown 等方式
public class MonitorThreadPool extends ThreadPoolExecutor {
// 保存任務開始執行的時間 , 當任務結束時 , 用任務結束時間減去開始時間計算任務執行時間
private ConcurrentHashMap<String, Date> startTime;
public MonitorThreadPool(int corePoolSize, int maximumPoolSize, long keepAliveTime,
TimeUnit unit, BlockingQueue<Runnable> workQueue) {
super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
this.startTime = new ConcurrentHashMap<>();
}
public static ExecutorService newCachedThreadPool() {
return new MonitorThreadPool(0, Integer.MAX_VALUE, 60L,
TimeUnit.SECONDS, new SynchronousQueue());
}
@Override
public void shutdown() {
System.out.println("已經執行的任務數:"+this.getCompletedTaskCount()
+",當前活動線程數:"+this.getActiveCount()+",當前排隊線程數:"+this.getQueue().size());
}
@Override
protected void beforeExecute(Thread t, Runnable r) {
startTime.put(String.valueOf(r.hashCode()), new Date());
super.beforeExecute(t,r);
}
@Override
protected void afterExecute(Runnable r, Throwable t) {
Date startDate = startTime.remove(String.valueOf(r.hashCode()));
Date finishDate = new Date();
Long diff = finishDate.getTime() - startDate.getTime();
System.out.print(" 任務耗時:"+diff);
System.out.print(" 初始線程數:"+this.getPoolSize());
System.out.print(" 核心線程數:"+this.getCorePoolSize());
System.out.print(" 正在執行的任務數量:"+this.getActiveCount());
System.out.print(" 已經執行的任務數"+this.getCompletedTaskCount());
System.out.print(" 任務總數:"+this.getTaskCount());
System.out.print(" 最大允許的線程數:"+this.getMaximumPoolSize());
System.out.print(" 線程空閒時間:"+this.getKeepAliveTime(TimeUnit.MILLISECONDS));
System.out.println();
super.afterExecute(r, t);
}
}
測試類
private static ExecutorService es = MonitorThreadPool.newCachedThreadPool();
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
for (int i = 0; i < 3; i++) {
es.execute(new TestMonitor());
}
es.shutdown();
}
Callable/Future使用以及原理分析
execute和submit的區別
execute | submit | |
接收參數 | Runnable | Runnable Callable |
如果出現異常會拋出 | 傳入一個 Callable,可以得到一個 Future 的返回值 | |
沒有返回值 | 傳入 Callable 會得到一個Future的返回值 |
Callable/Future案例演示
public class CallableAndFuture implements Callable<String> {
public static void main(String[] args) throws ExecutionException, InterruptedException {
CallableAndFuture callableAndFuture = new CallableAndFuture();
FutureTask futureTask = new FutureTask(callableAndFuture);
new Thread(futureTask).start();
System.out.println(futureTask.get());
}
@Override
public String call() throws Exception {
return "get return value";
}
}
使用場景
Dubbo異步調用、消息中間件的異步通信和利用FutureTask Callable Thread 對耗時任務做預處理
簡單分析
Callable是一個函數式接口,裏面只有一個call方法。子類可以對該方法進行重寫,並且該方法會有一個返回值。
FutureTask實現了RunnableFuture接口,該接口又繼承了Runnable和Future接口。Future接口表示了一個任務的生命週期,並提供了相應的方法來判斷是否已經完成或取消,以及獲取任務的結果和取消任務等。
FutureTask是Runnable和Future的結合,Runnable執行run方法計算結果,Future通過get方法獲取結果
public interface Future<V> {
boolean cancel(boolean mayInterruptIfRunning);
// 判斷 當前的 Future 是否被取消,返回 true 表示已取消
boolean isCancelled();
// 當前 Future 是否已經結束。包括運行完成、拋出異常以及取消,都表示當前Future已經結束
boolean isDone();
// 獲取 Future 的結果值,如果當前 Future 還沒有結束,那麼當前線程等待
// 直到 Future 運行結束,那麼會喚醒等待結果值的線程的。
V get() throws InterruptedException, ExecutionException;
V get(long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException;
}