線程池java,線程池參數

本文目錄一覽:

java線程池怎麼實現

要想理解清楚java線程池實現原理,明白下面幾個問題就可以了:

(1):線程池存在哪些狀態,這些狀態之間是如何進行切換的呢?

(2):線程池的種類有哪些?

(3):創建線程池需要哪些參數,這些參數的具體含義是什麼?

(4):將任務添加到線程池之後運行流程?

(5):線程池是怎麼做到重用線程的呢?

(6):線程池的關閉

首先回答第一個問題:線程池存在哪些狀態;

查看ThreadPoolExecutor源碼便知曉:

[java] view plain copy

// 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;

存在5種狀態:

1Running:可以接受新任務,同時也可以處理阻塞隊列裡面的任務;

2Shutdown:不可以接受新任務,但是可以處理阻塞隊列裡面的任務;

3Stop:不可以接受新任務,也不處理阻塞隊列裡面的任務,同時還中斷正在處理的任務;

4Tidying:屬於過渡階段,在這個階段表示所有的任務已經執行結束了,當前線程池中是不存在有效的線程的,並且將要調用terminated方法;

5Terminated:終止狀態,這個狀態是在調用完terminated方法之後所處的狀態;

那麼這5種狀態之間是如何進行轉換的呢?查看ThreadPoolExecutor源碼裡面的注釋便可以知道啦:

[java] view plain copy

* RUNNING – SHUTDOWN

*    On invocation of shutdown(), perhaps implicitly in finalize()

* (RUNNING or SHUTDOWN) – STOP

*    On invocation of shutdownNow()

* SHUTDOWN – TIDYING

*    When both queue and pool are empty

* STOP – TIDYING

*    When pool is empty

* TIDYING – TERMINATED

*    When the terminated() hook method has completed

從上面可以看到,在調用shutdown方法的時候,線程池狀態會從Running轉換成Shutdown;在調用shutdownNow方法的時候,線程池狀態會從Running/Shutdown轉換成Stop;在阻塞隊列為空同時線程池為空的情況下,線程池狀態會從Shutdown轉換成Tidying;在線程池為空的情況下,線程池狀態會從Stop轉換成Tidying;當調用terminated方法之後,線程池狀態會從Tidying轉換成Terminate;

在明白了線程池的各個狀態以及狀態之間是怎麼進行切換之後,我們來看看第二個問題,線程池的種類:

(1):CachedThreadPool:緩存線程池,該類線程池中線程的數量是不確定的,理論上可以達到Integer.MAX_VALUE個,這種線程池中的線程都是非核心線程,既然是非核心線程,那麼就存在超時淘汰機制了,當裡面的某個線程空閑時間超過了設定的超時時間的話,就會回收掉該線程;

(2):FixedThreadPool:固定線程池,這類線程池中是只存在核心線程的,對於核心線程來說,如果我們不設置allowCoreThreadTimeOut屬性的話是不存在超時淘汰機制的,這類線程池中的corePoolSize的大小是等於maximumPoolSize大小的,也就是說,如果線程池中的線程都處於活動狀態的話,如果有新任務到來,他是不會開闢新的工作線程來處理這些任務的,只能將這些任務放到阻塞隊列裡面進行等到,直到有核心線程空閑為止;

(3):ScheduledThreadPool:任務線程池,這種線程池中核心線程的數量是固定的,而對於非核心線程的數量是不限制的,同時對於非核心線程是存在超時淘汰機制的,主要適用於執行定時任務或者周期性任務的場景;

(4):SingleThreadPool:單一線程池,線程池裡面只有一個線程,同時也不存在非核心線程,感覺像是FixedThreadPool的特殊版本,他主要用於確保任務在同一線程中的順序執行,有點類似於進行同步吧;

接下來我們來看第三個問題,創建線程池需要哪些參數:

同樣查看ThreadPoolExecutor源碼,查看創建線程池的構造函數:

[java] view plain copy

public ThreadPoolExecutor(int corePoolSize,

int maximumPoolSize,

long keepAliveTime,

TimeUnit unit,

BlockingQueueRunnable workQueue,

ThreadFactory threadFactory,

RejectedExecutionHandler handler)

不管你調用的是ThreadPoolExecutor的哪個構造函數,最終都會執行到這個構造函數的,這個構造函數有7個參數,正是由於對這7個參數值的賦值不同,造成生成不同類型的線程池,比如我們常見的CachedThreadPoolExecutor、FixedThreadPoolExecutor

SingleThreadPoolExecutor、ScheduledThreadPoolExecutor,我們老看看這幾個參數的具體含義:

1corePoolSize:線程池中核心線程的數量;當提交一個任務到線程池的時候,線程池會創建一個線程來執行執行任務,即使有其他空閑的線程存在,直到線程數達到corePoolSize時不再創建,這時候會把提交的新任務放入到阻塞隊列中,如果調用了線程池的preStartAllCoreThreads方法,則會在創建線程池的時候初始化出來核心線程;

2maximumPoolSize:線程池允許創建的最大線程數;如果阻塞隊列已經滿了,同時已經創建的線程數小於最大線程數的話,那麼會創建新的線程來處理阻塞隊列中的任務;

3keepAliveTime:線程活動保持時間,指的是工作線程空閑之後繼續存活的時間,默認情況下,這個參數只有線程數大於corePoolSize的時候才會起作用,即當線程池中的線程數目大於corePoolSize的時候,如果某一個線程的空閑時間達到keepAliveTime,那麼這個線程是會被終止的,直到線程池中的線程數目不大於corePoolSize;如果調用allowCoreThreadTimeOut的話,在線程池中線程數量不大於corePoolSize的時候,keepAliveTime參數也可以起作用的,知道線程數目為0為止;

4unit:參數keepAliveTime的時間單位;

5workQueue:阻塞隊列;用於存儲等待執行的任務,有四種阻塞隊列類型,ArrayBlockingQueue(基於數組的有界阻塞隊列)、LinkedBlockingQueue(基於鏈表結構的阻塞隊列)、SynchronousQueue(不存儲元素的阻塞隊列)、PriorityBlockingQueue(具有優先級的阻塞隊列);

6threadFactory:用於創建線程的線程工廠;

7handler:當阻塞隊列滿了,且沒有空閑線程的情況下,也就是說這個時候,線程池中的線程數目已經達到了最大線程數量,處於飽和狀態,那麼必須採取一種策略來處理新提交的任務,我們可以自己定義處理策略,也可以使用系統已經提供給我們的策略,先來看看系統為我們提供的4種策略,AbortPolicy(直接拋出異常)、CallerRunsPolicy(只有調用者所在的線程來運行任務)、DiscardOldestPolicy(丟棄阻塞隊列中最近的一個任務,並執行當前任務)、Discard(直接丟棄);

接下來就是將任務添加到線程池之後的運行流程了;

我們可以調用submit或者execute方法,兩者最大的區別在於,調用submit方法的話,我們可以傳入一個實現Callable接口的對象,進而能在當前任務執行結束之後通過Future對象獲得任務的返回值,submit內部實際上還是執行的execute方法;而調用execute方法的話,是不能獲得任務執行結束之後的返回值的;此外,調用submit方法的話是可以拋出異常的,但是調用execute方法的話,異常在其內部得到了消化,也就是說異常在其內部得到了處理,不會向外傳遞的;

因為submit方法最終也是會執行execute方法的,因此我們只需要了解execute方法就可以了:

在execute方法內部會分三種情況來進行處理:

1:首先判斷當前線程池中的線程數量是否小於corePoolSize,如果小於的話,則直接通過addWorker方法創建一個新的Worker對象來執行我們當前的任務;

2:如果說當前線程池中的線程數量大於corePoolSize的話,那麼會嘗試將當前任務添加到阻塞隊列中,然後第二次檢查線程池的狀態,如果線程池不在Running狀態的話,會將剛剛添加到阻塞隊列中的任務移出,同時拒絕當前任務請求;如果第二次檢查發現當前線程池處於Running狀態的話,那麼會查看當前線程池中的工作線程數量是否為0,如果為0的話,就會通過addWorker方法創建一個Worker對象出來處理阻塞隊列中的任務;

3:如果原先線程池就不處於Running狀態或者我們剛剛將當前任務添加到阻塞隊列的時候出現錯誤的話,那麼會去嘗試通過addWorker創建新的Worker來處理當前任務,如果添加失敗的話,則拒絕當前任務請求;

可以看到在上面的execute方法中,我們僅僅只是檢查了當前線程池中的線程數量有沒有超過corePoolSize的情況,那麼當前線程池中的線程數量有沒有超過maximumPoolSize是在哪裡檢測的呢?實際上是在addWorker方法裡面了,我們可以看下addWorker裡面的一段代碼:

[java] view plain copy

if (wc = CAPACITY ||

wc = (core ? corePoolSize : maximumPoolSize))

return false;

如果當前線程數量超過maximumPoolSize的話,直接就會調用return方法,返回false;

其實到這裡我們很明顯可以知道,一個線程池中線程的數量實際上就是這個線程池中Worker的數量,如果Worker的大小超過了corePoolSize,那麼任務都在阻塞隊列裡面了,Worker是Java對我們任務的一個封裝類,他的聲明是醬紫的:

[java] view plain copy

private final class Worker

extends AbstractQueuedSynchronizer

implements Runnable

可以看到他實現了Runnable接口,他是在addWorker方法裡面通過new Worker(firstTask)創建的,我們來看看他的構造函數就知道了:

[java] view plain copy

Worker(Runnable firstTask) {

setState(-1); // inhibit interrupts until runWorker

this.firstTask = firstTask;

this.thread = getThreadFactory().newThread(this);

}

而這裡的firstTask其實就是我們調用execute或者submit的時候傳入的那個參數罷了,一般來說這些參數是實現Callable或者Runnable接口的;

在通過addWorker方法創建出來Worker對象之後,這個方法的最後會執行Worker內部thread屬性的start方法,而這個thread屬性實際上就是封裝了Worker的Thread,執行他的start方法實際上執行的是Worker的run方法,因為Worker是實現了Runnable接口的,在run方法裡面就會執行runWorker方法,而runWorker方法裡面首先會判斷當前我們傳入的任務是否為空,不為空的話直接就會執行我們通過execute或者submit方法提交的任務啦,注意一點就是我們雖然會通過submit方法提交實現了Callable接口的對象,但是在調用submit方法的時候,其實是會將Callable對象封裝成實現了Runnable接口對象的,不信我們看看submit方法源碼是怎麼實現的:

[java] view plain copy

public T FutureT submit(CallableT task) {

if (task == null) throw new NullPointerException();

RunnableFutureT ftask = newTaskFor(task);

execute(ftask);

return ftask;

}

看到沒有呢,實際上在你傳入實現了Callable接口對象的時候,在submit方法裡面是會將其封裝成RunnableFuture對象的,而RunnableFuture接口是繼承了Runnable接口的;那麼說白了其實就是直接執行我們提交任務的run方法了;如果為空的話,則會通過getTask方法從阻塞隊列裡面拿出一個任務去執行;在任務執行結束之後繼續從阻塞隊列裡面拿任務,直到getTask的返回值為空則退出runWorker內部循環,那麼什麼情況下getTask返回為空呢?查看getTask方法的源碼注釋可以知道:在Worker必須需要退出的情況下getTask會返回空,具體什麼情況下Worker會退出呢?(1):當Worker的數量超過maximumPoolSize的時候;(2):當線程池狀態為Stop的時候;(3):當線程池狀態為Shutdown並且阻塞隊列為空的時候;(4):使用等待超時時間從阻塞隊列中拿數據,但是超時之後仍然沒有拿到數據;

如果runWorker方法退出了它裡面的循環,那麼就說明當前阻塞隊列裡面是沒有任務可以執行的了,你可以看到在runWorker方法內部的finally語句塊中執行了processWorkerExit方法,用來對Worker對象進行回收操作,這個方法會傳入一個參數表示需要刪除的Worker對象;在進行Worker回收的時候會調用tryTerminate方法來嘗試關閉線程池,在tryTerminate方法裡面會檢查是否有Worker在工作,檢查線程池的狀態,沒問題的話就會將當前線程池的狀態過渡到Tidying,之後調用terminated方法,將線程池狀態更新到Terminated;

從上面的分析中,我們可以看出線程池運行的4個階段:

(1):poolSize corePoolSize,則直接創建新的線程(核心線程)來執行當前提交的任務;

(2):poolSize = corePoolSize,並且此時阻塞隊列沒有滿,那麼會將當前任務添加到阻塞隊列中,如果此時存在工作線程(非核心線程)的話,那麼會由工作線程來處理該阻塞隊列中的任務,如果此時工作線程數量為0的話,那麼會創建一個工作線程(非核心線程)出來;

(3):poolSize = corePoolSize,並且此時阻塞隊列已經滿了,那麼會直接創建新的工作線程(非核心線程)來處理阻塞隊列中的任務;

(4):poolSize = maximumPoolSize,並且此時阻塞隊列也滿了的話,那麼會觸發拒絕機制,具體決絕策略採用的是什麼就要看我們創建ThreadPoolExecutor的時候傳入的RejectExecutionHandler參數了;

接下來就是線程池是怎麼做到重用線程的呢?

個人認為線程池裡面重用線程的工作是在getTask裡面實現的,在getTask裡面是存在兩個for死循環嵌套的,他會不斷的從阻塞對列裡面取出需要執行的任務,返回給我們的runWorker方法裡面,而在runWorker方法裡面只要getTask返回的任務不是空就會執行該任務的run方法來處理它,這樣一直執行下去,直到getTask返回空為止,此時的情況就是阻塞隊列裡面沒有任務了,這樣一個線程處理完一個任務之後接着再處理阻塞隊列中的另一個任務,當然在線程池中的不同線程是可以並發處理阻塞隊列中的任務的,最後在阻塞隊列內部不存在任務的時候會去判斷是否需要回收Worker對象,其實Worker對象的個數就是線程池中線程的個數,至於什麼情況才需要回收,上面已經說了,就是四種情況了;

最後就是線程池是怎樣被關閉的呢?

涉及到線程池的關閉,需要用到兩個方法,shutdown和shutdownNow,他們都是位於ThreadPoolExecutor裡面的,對於shutdown的話,他會將線程池狀態切換成Shutdown,此時是不會影響對阻塞隊列中任務執行的,但是會拒絕執行新加進來的任務,同時會回收閑置的Worker;而shutdownNow方法會將線程池狀態切換成Stop,此時既不會再去處理阻塞隊列裡面的任務,也不會去處理新加進來的任務,同時會回收所有Worker;

超詳細的線程池使用解析

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中有哪幾種線程池

一:newCachedThreadPool

(1)緩存型池子,先查看池中有沒有以前建立的線程,如果有,就reuse,如果沒有,就建立一個新的線程加入池中;

(2)緩存型池子,通常用於執行一些生存周期很短的異步型任務;因此一些面向連接的daemon型server中用得不多;

(3)能reuse的線程,必須是timeout IDLE內的池中線程,缺省timeout是60s,超過這個IDLE時長,線程實例將被終止及移出池。

(4)注意,放入CachedThreadPool的線程不必擔心其結束,超過TIMEOUT不活動,其會自動被終止

二:newFixedThreadPool

(1)newFixedThreadPool與cacheThreadPool差不多,也是能reuse就用,但不能隨時建新的線程

(2)其獨特之處:任意時間點,最多只能有固定數目的活動線程存在,此時如果有新的線程要建立,只能放在另外的隊列中等待,直到當前的線程中某個線程終止直接被移出池子

(3)和cacheThreadPool不同,FixedThreadPool沒有IDLE機制(可能也有,但既然文檔沒提,肯定非常長,類似依賴上層的TCP或UDP IDLE機制之類的),所以FixedThreadPool多數針對一些很穩定很固定的正規並發線程,多用於服務器

(4)從方法的源代碼看,cache池和fixed 池調用的是同一個底層池,只不過參數不同:

fixed池線程數固定,並且是0秒IDLE(無IDLE)

cache池線程數支持0-Integer.MAX_VALUE(顯然完全沒考慮主機的資源承受能力),60秒IDLE

三:ScheduledThreadPool

(1)調度型線程池

(2)這個池子里的線程可以按schedule依次delay執行,或周期執行

四:SingleThreadExecutor

(1)單例線程,任意時間池中只能有一個線程

(2)用的是和cache池和fixed池相同的底層池,但線程數目是1-1,0秒IDLE(無IDLE)

【Java基礎】線程池的原理是什麼?

什麼是線程池?

總歸為:池化技術 —》數據庫連接池 緩存架構 緩存池 線程池 內存池,連接池,這種思想演變成緩存架構技術— JDK設計思想有千絲萬縷的聯繫

首先我們從最核心的ThreadPoolExecutor類中的方法講起,然後再講述它的實現原理,接着給出了它的使用示例,最後討論了一下如何合理配置線程池的大小。

Java 中的 ThreadPoolExecutor 類

java.uitl.concurrent.ThreadPoolExecutor 類是線程池中最核心的一個類,因此如果要透徹地了解Java 中的線程池,必須先了解這個類。下面我們來看一下 ThreadPoolExecutor 類的具體實現源碼。

在 ThreadPoolExecutor 類中提供了四個構造方法:

從上面的代碼可以得知,ThreadPoolExecutor 繼承了 AbstractExecutorService 類,並提供了四個構造器,事實上,通過觀察每個構造器的源碼具體實現,發現前面三個構造器都是調用的第四個構造器進行的初始化工作。

下面解釋下一下構造器中各個參數的含義:

corePoolSize:核心池的大小,這個參數跟後面講述的線程池的實現原理有非常大的關係。在創建了線程池後,默認情況下,線程池中並沒有任何線程,而是等待有任務到來才創建線程去執行任務,除非調用了prestartAllCoreThreads() 或者 prestartCoreThread()方法,從這 2 個方法的名字就可以看出,是預創建線程的意思,即在沒有任務到來之前就創建 corePoolSize 個線程或者一個線程。默認情況下,在創建了線程池後,線程池中的線程數為0,當有任務來之後,就會創建一個線程去執行任務,當線程池中的線程數目達到 corePoolSize 後,就會把到達的任務放到緩存隊列當中;

maximumPoolSize:線程池最大線程數,這個參數也是一個非常重要的參數,它表示在線程池中最多能創建多少個線程;

keepAliveTime:表示線程沒有任務執行時最多保持多久時間會終止。默認情況下,只有當線程池中的線程數大於 corePoolSize 時,keepAliveTime 才會起作用,直到線程池中的線程數不大於 corePoolSize,即當線程池中的線程數大於 corePoolSize 時,如果一個線程空閑的時間達到 keepAliveTime,則會終止,直到線程池中的線程數不超過 corePoolSize。但是如果調用了 allowCoreThreadTimeOut(boolean) 方法,在線程池中的線程數不大於 corePoolSize 時,keepAliveTime 參數也會起作用,直到線程池中的線程數為0;

unit:參數 keepAliveTime 的時間單位,有 7 種取值,在 TimeUnit 類中有 7 種靜態屬性:

workQueue:一個阻塞隊列,用來存儲等待執行的任務,這個參數的選擇也很重要,會對線程池的運行過程產生重大影響,一般來說,這裡的阻塞隊列有以下幾種選擇:

ArrayBlockingQueue 和 PriorityBlockingQueue 使用較少,一般使用 LinkedBlockingQueue 和 Synchronous。線程池的排隊策略與 BlockingQueue 有關。

threadFactory:線程工廠,主要用來創建線程;

handler:表示當拒絕處理任務時的策略,有以下四種取值:

具體參數的配置與線程池的關係將在下一節講述。

從上面給出的 ThreadPoolExecutor 類的代碼可以知道,ThreadPoolExecutor 繼承了AbstractExecutorService,我們來看一下 AbstractExecutorService 的實現:

AbstractExecutorService 是一個抽象類,它實現了 ExecutorService 接口。

我們接着看 ExecutorService 接口的實現:

而 ExecutorService 又是繼承了 Executor 接口,我們看一下 Executor 接口的實現:

java線程池原理

線程池把先前創建的線程重用於當前任務。這就解決了需要太多線程的問題,因此內存不足不是一個選擇。您甚至可以把線程池視為回收系統。它不止消除了用盡內存的選項,而且還使應用程序非常快速地響應,原因是當請求到達時已經存在一個線程。

工作流程步驟:

創建要執行的任務

使用執行程序創建執行程序池

把任務傳遞給執行程序池

關閉執行程序池

什麼是java線程池

找的資料,你看一下吧:

多線程技術主要解決處理器單元內多個線程執行的問題,它可以顯著減少處理器單元的閑置時間,增加處理器單元的吞吐能力。

假設一個服務器完成一項任務所需時間為:T1

創建線程時間,T2

在線程中執行任務的時間,T3

銷毀線程時間。

如果:T1

+

T3

遠大於

T2,則可以採用線程池,以提高服務器性能。

一個線程池包括以下四個基本組成部分:

1、線程池管理器(ThreadPool):用於創建並管理線程池,包括

創建線程池,銷毀線程池,添加新任務;

2、工作線程(PoolWorker):線程池中線程,在沒有任務時處於等待狀態,可以循環的執行任務;

3、任務接口(Task):每個任務必須實現的接口,以供工作線程調度任務的執行,它主要規定了任務的入口,任務執行完後的收尾工作,任務的執行狀態等;

4、任務隊列(taskQueue):用於存放沒有處理的任務。提供一種緩衝機制。

線程池技術正是關注如何縮短或調整T1,T3時間的技術,從而提高服務器程序性能的。它把T1,T3分別安排在服務器程序的啟動和結束的時間段或者一些空閑的時間段,這樣在服務器程序處理客戶請求時,不會有T1,T3的開銷了。

線程池不僅調整T1,T3產生的時間段,而且它還顯著減少了創建線程的數目,看一個例子:

假設一個服務器一天要處理50000個請求,並且每個請求需要一個單獨的線程完成。在線程池中,線程數一般是固定的,所以產生線程總數不會超過線程池中線程的數目,而如果服務器不利用線程池來處理這些請求則線程總數為50000。一般線程池大小是遠小於50000。所以利用線程池的服務器程序不會為了創建50000而在處理請求時浪費時間,從而提高效率。

原創文章,作者:小藍,如若轉載,請註明出處:https://www.506064.com/zh-hant/n/305058.html

(0)
打賞 微信掃一掃 微信掃一掃 支付寶掃一掃 支付寶掃一掃
小藍的頭像小藍
上一篇 2025-01-01 11:06
下一篇 2025-01-01 11:06

相關推薦

  • Java JsonPath 效率優化指南

    本篇文章將深入探討Java JsonPath的效率問題,並提供一些優化方案。 一、JsonPath 簡介 JsonPath是一個可用於從JSON數據中獲取信息的庫。它提供了一種DS…

    編程 2025-04-29
  • java client.getacsresponse 編譯報錯解決方法

    java client.getacsresponse 編譯報錯是Java編程過程中常見的錯誤,常見的原因是代碼的語法錯誤、類庫依賴問題和編譯環境的配置問題。下面將從多個方面進行分析…

    編程 2025-04-29
  • Java Bean加載過程

    Java Bean加載過程涉及到類加載器、反射機制和Java虛擬機的執行過程。在本文中,將從這三個方面詳細闡述Java Bean加載的過程。 一、類加載器 類加載器是Java虛擬機…

    編程 2025-04-29
  • Java騰訊雲音視頻對接

    本文旨在從多個方面詳細闡述Java騰訊雲音視頻對接,提供完整的代碼示例。 一、騰訊雲音視頻介紹 騰訊雲音視頻服務(Cloud Tencent Real-Time Communica…

    編程 2025-04-29
  • Java Milvus SearchParam withoutFields用法介紹

    本文將詳細介紹Java Milvus SearchParam withoutFields的相關知識和用法。 一、什麼是Java Milvus SearchParam without…

    編程 2025-04-29
  • Java 8中某一周的周一

    Java 8是Java語言中的一個版本,於2014年3月18日發布。本文將從多個方面對Java 8中某一周的周一進行詳細的闡述。 一、數組處理 Java 8新特性之一是Stream…

    編程 2025-04-29
  • 三星內存條參數用法介紹

    本文將詳細解釋三星內存條上面的各種參數,讓你更好地了解內存條並選擇適合自己的一款。 一、容量大小 容量大小是內存條最基本的參數,一般以GB為單位表示,常見的有2GB、4GB、8GB…

    編程 2025-04-29
  • Java判斷字符串是否存在多個

    本文將從以下幾個方面詳細闡述如何使用Java判斷一個字符串中是否存在多個指定字符: 一、字符串遍歷 字符串是Java編程中非常重要的一種數據類型。要判斷字符串中是否存在多個指定字符…

    編程 2025-04-29
  • Python3定義函數參數類型

    Python是一門動態類型語言,不需要在定義變量時顯示的指定變量類型,但是Python3中提供了函數參數類型的聲明功能,在函數定義時明確定義參數類型。在函數的形參後面加上冒號(:)…

    編程 2025-04-29
  • VSCode為什麼無法運行Java

    解答:VSCode無法運行Java是因為默認情況下,VSCode並沒有集成Java運行環境,需要手動添加Java運行環境或安裝相關插件才能實現Java代碼的編寫、調試和運行。 一、…

    編程 2025-04-29

發表回復

登錄後才能評論