執行緒池原理(二)
三、執行緒池原理:
管理同構執行緒的資源池。執行緒複用,執行緒處理完一個任務不被銷燬,可以繼承處理下一個任務;
為什麼要用執行緒池呢??
- 建立/銷燬執行緒伴隨著系統開銷,過於頻繁的建立/銷燬執行緒,會很大程度上影響處理效率;
- 避免執行緒併發數量過多,搶佔系統資源從而導致阻塞
- 對執行緒進行簡單管理,如延時執行、定時迴圈執行的策略等;
1.執行緒池實現 Executor這個介面,具體實現為ThreadPoolExecutor類(java.uitl.concurrent.ThreadPoolExecutor
完整的實現繼承是這樣的:
Executor這個介面 --->ExecutorService介面--->AbstractExecutorService抽象類--->ThreadPoolExecutor類;
2.ThreadPoolExecutor類有四個建構函式,通過配置引數來配置執行緒池:
//五個引數的建構函式 public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) //六個引數的建構函式-1 public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory) //六個引數的建構函式-2 public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, RejectedExecutionHandler handler) //七個引數的建構函式 public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) /** * 核心執行緒數為 (corePoolSize), * 最大執行緒數為(maximumPoolSize), * 存活時間(keepAliveTime), * 工作佇列為BlockingQueue<Runnable>(workQueue), * 執行緒工廠為預設的ThreadFactory (threadFactory), * 飽和策略(拒絕策略)為RejectedExecutionHandler : 丟擲異常(handler). */
3.執行緒池基本引數概念:
- 核心執行緒:(不幹活我也養你)執行緒池新建執行緒的時候,如果當前執行緒總數小於corePoolSize,則新建的是核心執行緒,如果超過corePoolSize,則新建的是非核心執行緒,核心執行緒預設情況下會一直存活線上程池中,即使這個核心執行緒啥也不幹(閒置狀態)。
- corePoolSize:在建立了執行緒池後,預設情況下,執行緒池中並沒有任何執行緒,而是等待有任務到來才建立執行緒去執行任務,除非呼叫了prestartAllCoreThreads()或者prestartCoreThread()方法,從這2個方法的名字就可以看出,是預建立執行緒的意思,即在沒有任務到來之前就建立corePoolSize個執行緒或者一個執行緒。預設情況下,在建立了執行緒池後,執行緒池中的執行緒數為0,當有任務來之後,就會建立一個執行緒去執行任務,當執行緒池中的執行緒數目達到corePoolSize後,就會把到達的任務放到快取隊列當中;
- maximumPoolSize:執行緒池最大執行緒數,它表示線上程池中最多能建立多少個執行緒;
- keepAliveTime:表示執行緒沒有任務執行時最多保持多久會終止.(預設情況 當執行緒數大於corePoolSize 該引數才會起效)
- 工作佇列:workQueue:一個阻塞佇列,用來儲存等待執行的任務;
- 飽和策略:handler:表示當拒絕處理任務時的策略
- .執行緒工廠:threadFactory,主要用來建立執行緒
4.工作佇列:workQueue:一個阻塞佇列,用來儲存等待執行的任務;
如果新請求的到達速率超過了執行緒池的處理速率,那麼新到來的請求將累積起來。線上程池中,這些請求會在一個由Executor管理的Runnable佇列中等待,而不會像執行緒那樣去競爭CPU資源..
workQueue的型別為BlockingQueue<Runnable>,通常可以取下面三種類型:
- 1)ArrayBlockingQueue:基於陣列的先進先出佇列,此佇列建立時必須指定大小;
- 2)LinkedBlockingQueue:基於連結串列的先進先出佇列,如果建立時沒有指定此佇列大小,則預設為Integer.MAX_VALUE;
- 3)synchronousQueue:這個佇列比較特殊,它不會儲存提交的任務,而是將直接新建一個執行緒來執行新來的任務。
5.飽和策略handler:表示當拒絕處理任務時的策略,有以下四種取值:
當執行緒池的任務快取佇列已滿並且執行緒池中的執行緒數目達到maximumPoolSize,如果還有任務到來就會採取任務拒絕策略,通常有以下四種策略:
1 2 3 4 |
|
6.執行緒工廠:threadFactory,主要用來建立執行緒;
在ThreadFactory中只定義了一個方法newThread,每當執行緒池需要建立一個新執行緒時都會呼叫這個方法;
7.執行緒池的關閉:
ThreadPoolExecutor提供了兩個方法,用於執行緒池的關閉,分別是shutdown()和shutdownNow(),其中:
- shutdown():不會立即終止執行緒池,而是要等所有任務快取佇列中的任務都執行完後才終止,但再也不會接受新的任務
- shutdownNow():立即終止執行緒池,並嘗試打斷正在執行的任務,並且清空任務快取佇列,返回尚未執行的任務
8.執行緒池容量的動態調整:
ThreadPoolExecutor提供了動態調整執行緒池容量大小的方法:setCorePoolSize()和setMaximumPoolSize(),
- setCorePoolSize:設定核心池大小
- setMaximumPoolSize:設定執行緒池最大能建立的執行緒數目大小
當上述引數從小變大時,ThreadPoolExecutor進行執行緒賦值,還可能立即建立新的執行緒來執行任務
9.執行緒池執行流程:
- 預設情況下建立執行緒池後不會立即建立執行緒,等到有任務提交時會建立執行緒((除非呼叫prestartCoreThread或prestartAllCoreThreads方法) );
- 當建立的執行緒數<corePoolSize時,每來一個一個任務,執行緒工廠都會建立一個執行緒取執行它;即使有現成空閒, 直到執行緒數達corePoolSize;
- 當建立的執行緒數 =corePoolSize時,再提交的任務就進入工作佇列等待,當有執行緒空閒時 就會去等待佇列取任務,繼續執行;
- 當執行緒數=corePoolSize 且等待佇列也滿了,如果這個時候還提交任務,則會繼續建立執行緒來處理,直到執行緒數達到最大執行緒數maximumPoolSize;
- 達到了執行緒池的maximumPoolSize 並且等待佇列也滿了,如果這個時候還提交任務,此時就要採取一些飽和策略(拒絕接受新任務 或者拋棄等等待佇列中的一些任務)異常;
- 如果某個執行緒的控線時間超過了keepAliveTime,那麼將被標記為可回收的,並且當前執行緒池的當前大小超過了核心執行緒數時,這個執行緒將被終止
10.模擬執行緒池內部機制:
- 假如有一個工廠,工廠裡面有10個工人,每個工人同時只能做一件任務。
- 因此只要當10個工人中有工人是空閒的,來了任務就分配給空閒的工人做;
- 當10個工人都有任務在做時,如果還來了任務,就把任務進行排隊等待;
- 如果說新任務數目增長的速度遠遠大於工人做任務的速度,那麼此時工廠主管可能會想補救措施,比如重新招4個臨時工人進來;
- 然後就將任務也分配給這4個臨時工人做;
- 如果說著14個工人做任務的速度還是不夠,此時工廠主管可能就要考慮不再接收新的任務或者拋棄前面的一些任務了。
- 當這14個工人當中有人空閒時,而新任務增長的速度又比較緩慢,工廠主管可能就考慮辭掉4個臨時工了,只保持原來的10個工人,畢竟請額外的工人是要花錢的。
這個例子中的corePoolSize就是10,而maximumPoolSize就是14(10+4)。
<1>向ThreadPoolExecutor新增任務:
通過ThreadPoolExecutor.execute(Runnable command)
方法即可向執行緒池內新增一個任務
<2>ThreadPoolExecutor的策略,當一個任務被新增進執行緒池時有以下情況:
- 執行緒數量未達到corePoolSize,則新建一個執行緒(核心執行緒)執行任務
- 執行緒數量達到了corePools,則將任務移入佇列等待
- 佇列已滿,新建執行緒(非核心執行緒)執行任務
- 佇列已滿,匯流排程數又達到了maximumPoolSize,就會由handler (RejectedExecutionHandler)丟擲異常
四、四種常用的執行緒池:
1.FixedThreadPool():定長執行緒池(正規):
- 可控制執行緒最大併發數(同時執行的執行緒數)
- 超出的執行緒會在佇列中等待
- 有指定的執行緒數的執行緒池,有核心的執行緒,裡面有固定的執行緒數量,響應的速度快。正規的併發執行緒,多用於伺服器。固定的執行緒數由系統資源設定。
核心執行緒是沒有超時機制的,佇列大小沒有限制,除非執行緒池關閉了核心執行緒才會被回收
建立方法:
//nThreads => 最大執行緒數即maximumPoolSize ExecutorService fixedThreadPool = Executors.newFixedThreadPool(int nThreads);
2.CachedThreadPool():可快取執行緒池:
- 執行緒數無限制
- 有空閒執行緒則複用空閒執行緒,若無空閒執行緒則新建執行緒
- 一定程式減少頻繁建立/銷燬執行緒,減少系統開銷
- 只有非核心執行緒,最大執行緒數很大(Int.Max(values)),它會為每一個任務新增一個新的執行緒,這邊有一個超時機制,當空閒的執行緒超過60s內沒有用到的話,就會被回收。
- 缺點就是沒有考慮到系統的實際記憶體大小。
建立方法:
ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
3.SingleThreadExecutor():單執行緒化的執行緒池:
- 有且僅有一個工作執行緒執行任務
- 所有任務按照指定順序執行,即遵循佇列的入隊出隊規則((FIFO, LIFO, 優先順序)
- 只有一個核心執行緒,就是一個孤家寡人,通過指定的順序將任務一個個丟到執行緒,都乖乖的排隊等待執行,不處理併發的操作,不會被回收。確定就是一個人幹活效率慢。
建立方法:
ExecutorService singleThreadPool = Executors.newSingleThreadPool();
4.ScheduledThreadPool():定長執行緒池
- 有延遲執行和週期重複執行的執行緒池。
- 它的核心執行緒池固定,非核心執行緒的數量沒有限制,但是閒置時會立即會被回收。
建立方法:
//nThreads => 最大執行緒數即maximumPoolSize ExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(int corePoolSize);
參考: