本文目錄一覽:
超詳細的線程池使用解析
Java 中線程池是運用場景最多的並發框架,幾乎所有需要異步或並發執行任務的程序都可以使用線程池。合理的使用線程池可以帶來多個好處:
(1) 降低資源消耗 。通過重複利用已創建的線程降低線程在創建和銷毀時造成的消耗。
(2) 提高響應速度 。當處理執行任務時,任務可以不需要等待線程的創建就能立刻執行。
(3) 提高線程的可管理性 。線程是稀缺資源,如果無限制的創建,不僅會消耗系統資源,還會降低系統的穩定性,使用線程池可以進行統一分配、調優和監控。
線程池的處理流程如上圖所示
線程池中通過 ctl 字段來表示線程池中的當前狀態,主池控制狀態 ctl 是 AtomicInteger 類型,包裝了兩個概念字段:workerCount 和 runState,workerCount 表示有效線程數,runState 表示是否正在運行、正在關閉等狀態。使用 ctl 字段表示兩個概念,ctl 的前 3 位表示線程池狀態,線程池中限制 workerCount 為(2^29 )-1(約 5 億)個線程,而不是 (2^31)-1(20 億)個線程。workerCount 是允許啟動和不允許停止的工作程序的數量。該值可能與實際的活動線程數暫時不同,例如,當 ThreadFactory 在被詢問時未能創建線程時,以及退出線程在終止前仍在執行記時。用戶可見的池大小報告為工作集的當前大小。 runState 提供主要的生命周期控制,取值如下表所示:
runState 隨着時間的推移而改變,在 awaitTermination() 方法中等待的線程將在狀態達到 TERMINATED 時返回。狀態的轉換為:
RUNNING – SHUTDOWN 在調用 shutdown() 時,可能隱含在 finalize() 中
(RUNNING 或 SHUTDOWN)- STOP 在調用 shutdownNow() 時
SHUTDOWN – TIDYING 當隊列和線程池都為空時
STOP – TIDYING 當線程池為空時
TIDYING – TERMINATED 當 terminate() 方法完成時
開發人員如果需要在線程池變為 TIDYING 狀態時進行相應的處理,可以通過重載 terminated() 函數來實現。
結合上圖說明線程池 ThreadPoolExecutor 執行流程,使用 execute() 方法提交任務到線程池中執行時分為4種場景:
(1)線程池中運行的線程數量小於 corePoolSize,創建新線程來執行任務。
(2)線程池中運行線程數量不小於 corePoolSize,將任務加入到阻塞隊列 BlockingQueue。
(3)如果無法將任務加入到阻塞隊列(隊列已滿),創建新的線程來處理任務(這裡需要獲取全局鎖)。
(4)當創建新的線程數量使線程池中當前運行線程數量超過 maximumPoolSize,線程池中拒絕任務,調用 RejectedExecutionHandler.rejectedExecution() 方法處理。
源碼分析:
線程池創建線程時,會將線程封裝成工作線程 Worker,Worker 在執行完任務後,還會循環獲取工作隊列里的任務來執行。
創建線程池之前,首先要知道創建線程池中的核心參數:
corePoolSize (核心線程數大小):當提交任務到線程池時,線程池會創建一個線程來執行任務,即使其他空閑的基本線程能夠執行新任務也會創建線程,直到需要執行的任務數大於核心線程數時就不再創建。
runnableTaskQueue (任務隊列):用於保存等待執行任務的阻塞隊列。一般選擇以下幾種:
ArrayBlockingQueue:基於數組的有界阻塞隊列,按照 FIFO 原則對元素進行排序。
LinkedBlockingQueue:基於鏈表的阻塞隊列,按照 FIFO 原則對元素進行排序。
SynchronousQueue:同步阻塞隊列,也是不存儲元素的阻塞隊列。每一個插入操作必須要等到另一個 線程調用移除操作,否則插入操作一直處於阻塞狀態。
PriorityBlockingQueue:優先阻塞隊列,一個具有優先級的無限阻塞隊列。
maximumPoolSize (最大線程數大小):線程池允許創建的最大線程數,當隊列已滿,並且線程池中的線程數小於最大線程數,則線程池會創建新的線程執行任務。當使用無界隊列時,此參數無用。
RejectedExecutionHandler (拒絕策略):當任務隊列和線程池都滿了,說明線程池處於飽和狀態,那麼必須使用拒絕策略來處理新提交的任務。JDK 內置拒絕策略有以下 4 種:
AbortPolicy:直接拋出異常
CallerRunsPolicy:使用調用者所在的線程來執行任務
DiscardOldestPolicy:丟棄隊列中最近的一個任務來執行當前任務
DiscardPolicy:直接丟棄不處理
可以根據應用場景來實現 RejectedExecutionHandler 接口自定義處理策略。
keepAliveTime (線程存活時間):線程池的工作線程空閑後,保持存活的時間。
TimeUnit (存活時間單位):可選單位DAYS(天)、HOURS(小時)、MINUTES(分鐘)、MILLISECONDS(毫秒)、MICROSECONDS(微妙)、NANOSECONDS(納秒)。
ThreadFactory (線程工廠):可以通過線程工廠給創建出來的線程設置有意義的名字。
創建線程池主要分為兩大類,第一種是通過 Executors 工廠類創建線程池,第二種是自定義創建線程池。根據《阿里java開發手冊》中的規範,線程池不允許使用 Executors 去創建,原因是規避資源耗盡的風險。
創建一個單線程化的線程池
創建固定線程數的線程池
以上兩種創建線程池方式使用鏈表阻塞隊列來存放任務,實際場景中可能會堆積大量請求導致 OOM
創建可緩存線程池
允許創建的線程數量最大為 Integer.MAX_VALUE,當創建大量線程時會導致 CPU 處於重負載狀態和 OOM 的發生
向線程池提交任務可以使用兩個方法,分別為 execute() 和 submit()。
execute() 方法用於提交不需要返回值的任務,所以無法判斷任務是否被線程池執行成功。execute() 方法中傳入的是 Runnable 類的實例。
submit() 方法用於提交需要返回值的任務。線程池會返回一個 Future 類型的對象,通過 future 對象可以判斷任務是否執行成功,並且可以通過 future 的 get() 方法來獲取返回值。get() 方法會阻塞當前線程直到任務完成,使用 get(long timeout, TimeUnit unit)方法會阻塞當前線程一段時間後立即返回,這時候可能任務沒有執行完。
可以通過調用線程池的 shutdown() 或shutdownNow() 方法來關閉線程池。他們的原理是遍歷線程池中的工作線程,然後逐個調用 interrupt() 方法來中斷線程,所以無法響應中斷任務可能永遠無法終止。
shutdown() 和 shutdownNow() 方法的區別在於 shutdownNow 方法首先將線程池的狀態設置為 STOP,然後嘗試停止正在執行或暫停任務的線程,並返回等待執行任務的列表,而 shutdown 只是將線程池的狀態設置成 SHUTDOWN 狀態,然後中斷所有沒有正在執行任務的線程。
線程池使用面臨的核心的問題在於: 線程池的參數並不好配置 。一方面線程池的運行機制不是很好理解,配置合理需要強依賴開發人員的個人經驗和知識;另一方面,線程池執行的情況和任務類型相關性較大,IO 密集型和 CPU 密集型的任務運行起來的情況差異非常大,這導致業界並沒有一些成熟的經驗策略幫助開發人員參考。
(1)以任務型為參考的簡單評估:
假設線程池大小的設置(N 為 CPU 的個數)
如果純計算的任務,多線程並不能帶來性能提升,因為 CPU 處理能力是稀缺的資源,相反導致較多的線程切換的花銷,此時建議線程數為 CPU 數量或+1;—-為什麼+1?因為可以防止 N 個線程中有一個線程意外中斷或者退出,CPU 不會空閑等待。
如果是 IO 密集型應用, 則線程池大小設置為 2N+1. 線程數 = CPU 核數 目標 CPU 利用率 (1 + 平均等待時間 / 平均工作時間)
(2)以任務數為參考的理想狀態評估:
1)默認值
2)如何設置 * 需要根據相關值來決定 – tasks :每秒的任務數,假設為500~1000 – taskCost:每個任務花費時間,假設為0.1s – responsetime:系統允許容忍的最大響應時間,假設為1s
以上都為理想值,實際情況下要根據機器性能來決定。如果在未達到最大線程數的情況機器 cpu load 已經滿了,則需要通過升級硬件和優化代碼,降低 taskCost 來處理。
(僅為簡單的理想狀態的評估,可作為線程池參數設置的一個參考)
與主業務無直接數據依賴的從業務可以使用異步線程池來處理,在項目初始化時創建線程池並交給將從業務中的任務提交給異步線程池執行能夠縮短響應時間。
嚴禁在業務代碼中起線程!!!
當任務需要按照指定順序(FIFO, LIFO, 優先級)執行時,推薦創建使用單線程化的線程池。
本文章主要說明了線程池的執行原理和創建方式以及推薦線程池參數設置和一般使用場景。在開發中,開發人員需要根據業務來合理的創建和使用線程池達到降低資源消耗,提高響應速度的目的。
原文鏈接:
Java線程池中的核心線程是如何被重複利用的
Java線程池中的核心線程是如何被重複利用的?
引言
在Java開發中,經常需要創建線程去執行一些任務,實現起來也非常方便,但如果並發的線程數量很多,並且每個線程都是執行一個時間很短的任務就結束了,這樣頻繁創建線程就會大大降低系統的效率,因為頻繁創建線程和銷毀線程需要時間。此時,我們很自然會想到使用線程池來解決這個問題。
使用線程池的好處:
降低資源消耗。java中所有的池化技術都有一個好處,就是通過復用池中的對象,降低系統資源消耗。設想一下如果我們有n多個子任務需要執行,如果我們為每個子任務都創建一個執行線程,而創建線程的過程是需要一定的系統消耗的,最後肯定會拖慢整個系統的處理速度。而通過線程池我們可以做到復用線程,任務有多個,但執行任務的線程可以通過線程池來複用,這樣減少了創建線程的開銷,系統資源利用率得到了提升。
降低管理線程的難度。多線程環境下對線程的管理是最容易出現問題的,而線程池通過框架為我們降低了管理線程的難度。我們不用再去擔心何時該銷毀線程,如何最大限度的避免多線程的資源競爭。這些事情線程池都幫我們代勞了。
提升任務處理速度。線程池中長期駐留了一定數量的活線程,當任務需要執行時,我們不必先去創建線程,線程池會自己選擇利用現有的活線程來處理任務。
很顯然,線程池一個很顯著的特徵就是“長期駐留了一定數量的活線程”,避免了頻繁創建線程和銷毀線程的開銷,那麼它是如何做到的呢?我們知道一個線程只要執行完了run()方法內的代碼,這個線程的使命就完成了,等待它的就是銷毀。既然這是個“活線程”,自然是不能很快就銷毀的。為了搞清楚這個“活線程”是如何工作的,下面通過追蹤源碼來看看能不能解開這個疑問。
分析方法
在分析源碼之前先來思考一下要怎麼去分析,源碼往往是比較複雜的,如果知識儲備不夠豐厚,很有可能會讀不下去,或者讀岔了。一般來講要時刻緊跟着自己的目標來看代碼,跟目標關係不大的代碼可以不理會它,一些異常的處理也可以暫不理會,先看正常的流程。就我們現在要分析的源碼而言,目標就是看看線程是如何被複用的。那麼對於線程池的狀態的管理以及非正常狀態下的處理代碼就可以不理會,具體來講,在ThreadPollExcutor類中,有一個字段 private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0)); 是對線程池的運行狀態和線程池中有效線程的數量進行控制的, 它包含兩部分信息: 線程池的運行狀態 (runState) 和線程池內有效線程的數量 (workerCount),還有幾個對ctl進行計算的方法:
// 獲取運行狀態
private static int runStateOf(int c) { return c ~CAPACITY; }
// 獲取活動線程數
private static int workerCountOf(int c) { return c CAPACITY; }123456
以上兩個方法在源碼中經常用到,結合我們的目標,對運行狀態的一些判斷及處理可以不用去管,而對當前活動線程數要加以關注等等。
下面將遵循這些原則來分析源碼。
解惑
當我們要向線程池添加一個任務時是調用ThreadPollExcutor對象的execute(Runnable command)方法來完成的,所以先來看看ThreadPollExcutor類中的execute(Runnable command)方法的源碼:
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);
}123456789101112131415161718192021
按照我們在分析方法中提到的一些原則,去掉一些相關性不強的代碼,看看核心代碼是怎樣的。
// 為分析而簡化後的代碼
public void execute(Runnable command) {
int c = ctl.get();
if (workerCountOf(c) corePoolSize) {
// 如果當前活動線程數小於corePoolSize,則新建一個線程放入線程池中,並把任務添加到該線程中
if (addWorker(command, true))
return;
c = ctl.get();
}
// 如果當前活動線程數大於等於corePoolSize,則嘗試將任務放入緩存隊列
if (workQueue.offer(command)) {
int recheck = ctl.get();
if (workerCountOf(recheck) == 0)
addWorker(null, false);
}else {
// 緩存已滿,新建一個線程放入線程池,並把任務添加到該線程中(此時新建的線程相當於非核心線程)
addWorker(command, false)
}
}12345678910111213141516171819202122
這樣一看,邏輯應該清晰很多了。
如果 當前活動線程數 指定的核心線程數,則創建並啟動一個線程來執行新提交的任務(此時新建的線程相當於核心線程);
如果 當前活動線程數 = 指定的核心線程數,且緩存隊列未滿,則將任務添加到緩存隊列中;
如果 當前活動線程數 = 指定的核心線程數,且緩存隊列已滿,則創建並啟動一個線程來執行新提交的任務(此時新建的線程相當於非核心線程);
接下來看 addWorker(Runnable firstTask, boolean core)方法
private boolean addWorker(Runnable firstTask, boolean core) {
retry:
for (;;) {
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);
if (wc = CAPACITY ||
wc = (core ? corePoolSize : maximumPoolSize))
return false;
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 {
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);
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;
}12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667
同樣,我們也來簡化一下:
// 為分析而簡化後的代碼
private boolean addWorker(Runnable firstTask, boolean core) {
int wc = workerCountOf(c);
if (wc = (core ? corePoolSize : maximumPoolSize))
// 如果當前活動線程數 = 指定的核心線程數,不創建核心線程
// 如果當前活動線程數 = 指定的最大線程數,不創建非核心線程
return false;
boolean workerStarted = false;
boolean workerAdded = false;
Worker w = null;
try {
// 新建一個Worker,將要執行的任務作為參數傳進去
w = new Worker(firstTask);
final Thread t = w.thread;
if (t != null) {
workers.add(w);
workerAdded = true;
if (workerAdded) {
// 啟動剛剛新建的那個worker持有的線程,等下要看看這個線程做了啥
t.start();
workerStarted = true;
}
}
} finally {
if (! workerStarted)
addWorkerFailed(w);
}
return workerStarted;
}1234567891011121314151617181920212223242526272829303132
看到這裡,我們大概能猜測到,addWorker方法的功能就是新建一個線程並啟動這個線程,要執行的任務應該就是在這個線程中執行。為了證實我們的這種猜測需要再來看看Worker這個類。
private final class Worker
extends AbstractQueuedSynchronizer
implements Runnable{
// ….
}
Worker(Runnable firstTask) {
setState(-1); // inhibit interrupts until runWorker
this.firstTask = firstTask;
this.thread = getThreadFactory().newThread(this);
}123456789101112
從上面的Worker類的聲明可以看到,它實現了Runnable接口,以及從它的構造方法中可以知道待執行的任務賦值給了它的變量firstTask,並以它自己為參數新建了一個線程賦值給它的變量thread,那麼運行這個線程的時候其實就是執行Worker的run()方法,來看一下這個方法:
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);
}
}123456789101112131415161718192021222324252627282930313233343536373839404142434445464748
在run()方法中只調了一下 runWorker(this) 方法,再來簡化一下這個 runWorker() 方法
// 為分析而簡化後的代碼
final void runWorker(Worker w) {
Runnable task = w.firstTask;
w.firstTask = null;
while (task != null || (task = getTask()) != null) {
try {
task.run();
} finally {
task = null;
}
}
}12345678910111213
很明顯,runWorker()方法裡面執行了我們新建Worker對象時傳進去的待執行的任務,到這裡為止貌似這個worker的run()方法就執行完了,既然執行完了那麼這個線程也就沒用了,只有等待虛擬機銷毀了。那麼回顧一下我們的目標:Java線程池中的核心線程是如何被重複利用的?好像並沒有重複利用啊,新建一個線程,執行一個任務,然後就結束了,銷毀了。沒什麼特別的啊,難道有什麼地方漏掉了,被忽略了?再仔細看一下runWorker()方法的代碼,有一個while循環,當執行完firstTask後task==null了,那麼就會執行判斷條件 (task = getTask()) != null,我們假設這個條件成立的話,那麼這個線程就不止只執行一個任務了,可以執行多個任務了,也就實現了重複利用了。答案呼之欲出了,接着看getTask()方法
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.
if (rs = SHUTDOWN (rs = 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;
}
}
}1234567891011121314151617181920212223242526272829303132333435363738
老規矩,簡化一下代碼來看:
// 為分析而簡化後的代碼
private Runnable getTask() {
boolean timedOut = false;
for (;;) {
int c = ctl.get();
int wc = workerCountOf(c);
// timed變量用於判斷是否需要進行超時控制。
// allowCoreThreadTimeOut默認是false,也就是核心線程不允許進行超時;
// wc corePoolSize,表示當前線程池中的線程數量大於核心線程數量;
// 對於超過核心線程數量的這些線程,需要進行超時控制
boolean timed = allowCoreThreadTimeOut || wc corePoolSize;
if (timed timedOut) {
// 如果需要進行超時控制,且上次從緩存隊列中獲取任務時發生了超時,那麼嘗試將workerCount減1,即當前活動線程數減1,
// 如果減1成功,則返回null,這就意味着runWorker()方法中的while循環會被退出,其對應的線程就要銷毀了,也就是線程池中少了一個線程了
if (compareAndDecrementWorkerCount(c))
return null;
continue;
}
try {
Runnable r = timed ?
workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
workQueue.take();
// 注意workQueue中的poll()方法與take()方法的區別
//poll方式取任務的特點是從緩存隊列中取任務,最長等待keepAliveTime的時長,取不到返回null
//take方式取任務的特點是從緩存隊列中取任務,若隊列為空,則進入阻塞狀態,直到能取出對象為止
if (r != null)
return r;
timedOut = true;
} catch (InterruptedException retry) {
timedOut = false;
}
}
}123456789101112131415161718192021222324252627282930313233343536373839
從以上代碼可以看出,getTask()的作用是
如果當前活動線程數大於核心線程數,當去緩存隊列中取任務的時候,如果緩存隊列中沒任務了,則等待keepAliveTime的時長,此時還沒任務就返回null,這就意味着runWorker()方法中的while循環會被退出,其對應的線程就要銷毀了,也就是線程池中少了一個線程了。因此只要線程池中的線程數大於核心線程數就會這樣一個一個地銷毀這些多餘的線程。
如果當前活動線程數小於等於核心線程數,同樣也是去緩存隊列中取任務,但當緩存隊列中沒任務了,就會進入阻塞狀態,直到能取出任務為止,因此這個線程是處於阻塞狀態的,並不會因為緩存隊列中沒有任務了而被銷毀。這樣就保證了線程池有N個線程是活的,可以隨時處理任務,從而達到重複利用的目的。
小結
通過以上的分析,應該算是比較清楚地解答了“線程池中的核心線程是如何被重複利用的”這個問題,同時也對線程池的實現機制有了更進一步的理解:
當有新任務來的時候,先看看當前的線程數有沒有超過核心線程數,如果沒超過就直接新建一個線程來執行新的任務,如果超過了就看看緩存隊列有沒有滿,沒滿就將新任務放進緩存隊列中,滿了就新建一個線程來執行新的任務,如果線程池中的線程數已經達到了指定的最大線程數了,那就根據相應的策略拒絕任務。
當緩存隊列中的任務都執行完了的時候,線程池中的線程數如果大於核心線程數,就銷毀多出來的線程,直到線程池中的線程數等於核心線程數。此時這些線程就不會被銷毀了,它們一直處於阻塞狀態,等待新的任務到來。
注意:
本文所說的“核心線程”、“非核心線程”是一個虛擬的概念,是為了方便描述而虛擬出來的概念,在代碼中並沒有哪個線程被標記為“核心線程”或“非核心線程”,所有線程都是一樣的,只是當線程池中的線程多於指定的核心線程數量時,會將多出來的線程銷毀掉,池中只保留指定個數的線程。那些被銷毀的線程是隨機的,可能是第一個創建的線程,也可能是最後一個創建的線程,或其它時候創建的線程。一開始我以為會有一些線程被標記為“核心線程”,而其它的則是“非核心線程”,在銷毀多餘線程的時候只銷毀那些“非核心線程”,而“核心線程”不被銷毀。這種理解是錯誤的。
另外還有一個重要的接口 BlockingQueue 值得去了解,它定義了一些入隊出隊同步操作的方法,還可以阻塞,作用很大。
java 什麼是線程池及為什麼要使用線程池
創建線程要花費昂貴的資源和時間,如果任務來了才創建線程那麼響應時間會變長,而且一個進程能創建的線程數有限。為了避免這些問題,在程序啟動的時候就創建若干線程來響應處理,它們被稱為線程池,裡面的線程叫工作線程。從JDK1.5開始,Java
API提供了Executor框架讓你可以創建不同的線程池。比如單線程池,每次處理一個任務;數目固定的線程池或者是緩存線程池(一個適合很多生存期短的任務的程序的可擴展線程池)。
原創文章,作者:OCT8B,如若轉載,請註明出處:https://www.506064.com/zh-hant/n/130404.html