您現在的位置是:首頁 > 綜合

Java基礎——Java多執行緒(什麼是執行緒池?)

  • 由 程式設計師進階之路 發表于 綜合
  • 2022-01-08
簡介這個佇列接收到任務時,首先先入隊,只有達到了指定的延時時間,才會執行任務2.4.3 ThreadPoolExecutor的策略當一個任務被新增進執行緒池時:執行緒數量未達到corePoolSize,則新建一個執行緒(核心執行緒)執行任務執行

執行緒池開新事務嗎

1 基本概括

Java基礎——Java多執行緒(什麼是執行緒池?)

2 主要介紹

2.1 執行緒池的概念

執行緒池是一種多執行緒處理形式,處理過程中將任務新增到佇列,然後在建立執行緒後自動啟動這些任務。執行緒池執行緒都是後臺執行緒。

執行緒池內部結構 :

1。執行緒池管理器:負責執行緒建立、銷燬、新增任務等;

2。工作執行緒: 執行緒池建立的正在工作的執行緒;

3。任務佇列( BlockingQueue ):執行緒滿了之後,可以放到任務佇列中,起到一定的緩衝;

4。任務:要求實現統一的介面,方便處理和執行;

2.2 執行緒池的優點

可以將任務的提交和執行策略解耦,便於統一管理任務執行策略,好維護,比如延時執行,設定等 待時間,超時自動失敗等。

2。提高效能,用已建立的執行緒執行任務,減少建立和銷燬執行緒的開銷。

3。約束最大執行緒併發數,防止無止境建立執行緒造成效能變差以及程式死掉。

4。活躍執行緒數、最大執行緒數等引數可配置,方便進行效能調優。

2.3 執行緒池的狀態

執行緒池的5種狀態:Running、ShutDown、Stop、Tidying、Terminated。

執行緒池各個狀態切換框架圖:

Java基礎——Java多執行緒(什麼是執行緒池?)

1.RUNNING

狀態說明:執行緒池處在RUNNING狀態時,能夠接收新任務,以及對已新增的任務進行處理。

狀態切換:執行緒池的初始化狀態是RUNNING。換句話說,執行緒池被一旦被建立,就處於RUNNING狀態,並且執行緒池中的任務數為0!

2.SHUTDOWN

狀態說明:執行緒池處在SHUTDOWN狀態時,不接收新任務,但能處理已新增的任務。

狀態切換:呼叫執行緒池的shutdown()介面時,執行緒池由RUNNING -> SHUTDOWN。

3.STOP

狀態說明:執行緒池處在STOP狀態時,不接收新任務,不處理已新增的任務,並且會中斷正在處理的任務。

狀態切換:呼叫執行緒池的shutdownNow()介面時,執行緒池由(RUNNING or SHUTDOWN ) -> STOP。

4.TIDYING

狀態說明:當所有的任務已終止,ctl記錄的”任務數量”為0,執行緒池會變為TIDYING狀態。當執行緒池變為TIDYING狀態時,會執行鉤子函式terminated()。terminated()在ThreadPoolExecutor類中是空的,若使用者想線上程池變為TIDYING時,進行相應的處理;可以透過過載terminated()函式來實現。

狀態切換:當執行緒池在SHUTDOWN狀態下,阻塞佇列為空並且執行緒池中執行的任務也為空時,就會由 SHUTDOWN -> TIDYING。

當執行緒池在STOP狀態下,執行緒池中執行的任務為空時,就會由STOP -> TIDYING。

5.TERMINATED

狀態說明:執行緒池徹底終止,就變成TERMINATED狀態。

狀態切換:執行緒池處在TIDYING狀態時,執行完terminated()之後,就會由 TIDYING -> TERMINATED。

2.4 執行緒池的使用場景

使用的場景:

1、需要大量的執行緒來完成任務,且完成任務的時間比較短。 WEB伺服器完成網頁請求這樣的任務,使用執行緒池技術是非常合適的。因為單個任務小,而任務數量巨大,你可以想象一個熱門網站的點選次數。 但對於長時間的任務,比如一個Telnet連線請求,執行緒池的優點就不明顯了。因為Telnet會話時間比執行緒的建立時間大多了。

2、對效能要求苛刻的應用,比如要求伺服器迅速響應客戶請求。

3、接受突發性的大量請求,但不至於使伺服器因此產生大量執行緒的應用。突發性大量客戶請求,在沒有執行緒池情況下,將產生大量執行緒,雖然理論上大部分作業系統執行緒數目最大值不是問題,短時間內產生大量執行緒可能使記憶體到達極限,並出現“OutOfMemory”的錯誤。

不使用執行緒池的場景:

1、如果需要使一個任務具有特定優先順序

2、如果具有可能會長時間執行(並因此阻塞其他任務)的任務

3、如果需要將執行緒放置到單執行緒單元中(執行緒池中的執行緒均處於多執行緒單元中)

2.5 執行緒池的使用步驟

Java基礎——Java多執行緒(什麼是執行緒池?)

2.6 ThreadPoolExecutor類

2.4.1 引數說明

corePoolSize:表示核心執行緒池的大小。當提交一個任務時,如果當前核心執行緒池的執行緒個數沒有達到 corePoolSize,則會建立新的執行緒來執行所提交的任務,即使當前核心執行緒池有空閒的執行緒。如果當前核心執行緒池的執行緒個數已經達到了 corePoolSize,則不再重新建立執行緒。如果呼叫了prestartCoreThread()或者 prestartAllCoreThreads(),執行緒池建立的時候所有的核心執行緒都會被建立並且啟動。maximumPoolSize:表示執行緒池能建立執行緒的最大個數。如果當阻塞佇列已滿時,並且當前執行緒池執行緒個數沒有超過 maximumPoolSize 的話,就會建立新的執行緒來執行任務。keepAliveTime:空閒執行緒存活時間。如果當前執行緒池的執行緒個數已經超過了 corePoolSize,並且執行緒空閒時間超過了 keepAliveTime 的話,就會將這些空閒執行緒銷燬,這樣可以儘可能降低系統資源消耗。 unit:時間單位。為 keepAliveTime 指定時間單位。workQueue:阻塞佇列。用於儲存任務的阻塞佇列,關於阻塞佇列可以看這篇文章。可以使用ArrayBlockingQueue, LinkedBlockingQueue, SynchronousQueue, PriorityBlockingQueue。 threadFactory:建立執行緒的工程類。可以透過指定執行緒工廠為每個創建出來的執行緒設定更有意義的名字,如果出現併發問題,也方便查詢問題原因。 handler:飽和策略。當執行緒池的阻塞佇列已滿和指定的執行緒都已經開啟,說明當前執行緒池已經處於飽和狀態了,那麼就需要採用一種策略來處理這種情況。採用的策略有這幾種:AbortPolicy: 直接拒絕所提交的任務,並丟擲RejectedExecutionException異常;CallerRunsPolicy:只用呼叫者所在的執行緒來執行任務;DiscardPolicy:不處理直接丟棄掉任務;DiscardOldestPolicy:丟棄掉阻塞佇列中存放時間最久的任務,執行當前任務

2.4.2 工作佇列workQueue

該執行緒池中的任務佇列:維護著等待執行的Runnable物件

當所有的核心執行緒都在幹活時,新新增的任務會被新增到這個佇列中等待處理,如果佇列滿了,則新建非核心執行緒執行任務

常用的workQueue型別:

SynchronousQueue

:這個佇列接收到任務的時候,會直接提交給執行緒處理,而不保留它,如果所有執行緒都在工作怎麼辦?那就新建一個執行緒來處理這個任務!所以為了保證不出現<執行緒數達到了maximumPoolSize而不能新建執行緒>的錯誤,使用這個型別佇列的時候,maximumPoolSize一般指定成Integer。MAX_VALUE,即無限大

LinkedBlockingQueue

:這個佇列接收到任務的時候,如果當前執行緒數小於核心執行緒數,則新建執行緒(核心執行緒)處理任務;如果當前執行緒數等於核心執行緒數,則進入佇列等待。由於這個佇列沒有最大值限制,即所有超過核心執行緒數的任務都將被新增到佇列中,這也就導致了maximumPoolSize的設定失效,因為匯流排程數永遠不會超過corePoolSize

ArrayBlockingQueue

:可以限定佇列的長度,接收到任務的時候,如果沒有達到corePoolSize的值,則新建執行緒(核心執行緒)執行任務,如果達到了,則入隊等候,如果佇列已滿,則新建執行緒(非核心執行緒)執行任務,又如果匯流排程數到了maximumPoolSize,並且佇列也滿了,則發生錯誤

DelayQueue

:佇列內元素必須實現Delayed介面,這就意味著你傳進去的任務必須先實現Delayed介面。這個佇列接收到任務時,首先先入隊,只有達到了指定的延時時間,才會執行任務

2.4.3 ThreadPoolExecutor的策略

當一個任務被新增進執行緒池時:

執行緒數量未達到corePoolSize,則新建一個執行緒(核心執行緒)執行任務

執行緒數量達到了corePools,則將任務移入佇列等待

佇列已滿,新建執行緒(非核心執行緒)執行任務

佇列已滿,匯流排程數又達到了maximumPoolSize,就會由 RejectedExecutionHandler丟擲異常

2.4.4 可建立的執行緒池

1.可快取執行緒池: CachedThreadPool

a。沒有最大執行緒數限制,想建立多少就建立多少

b。有空閒執行緒就用空閒執行緒,沒有就建立新執行緒

c。複用空閒執行緒能減少一部分重複建立銷燬的開銷

2. 定長執行緒池:FixedThreadPool

a。用來控制執行緒最大併發數。

b。如果沒有空閒執行緒,剩下的任務會在佇列等待有空閒執行緒執行。

3.支援定時和週期反覆執行的定長執行緒池:ScheduledThreadPool

4.單執行緒的執行緒池:SingleThreadExecutor

執行緒池裡只有一個執行緒,按照佇列的出入規則來序列執行任務,例如先進先出。

2.4.5 拒絕策略

一般我們建立執行緒池時,為防止資源被耗盡,任務佇列都會選擇建立有界任務佇列,這種模式下如果出現任務佇列已滿且執行緒池建立的執行緒數達到你設定的最大執行緒數時,這時就需要你指定ThreadPoolExecutor的RejectedExecutionHandler引數即合理的拒絕策略,來處理執行緒池“超載”的情況。ThreadPoolExecutor自帶的拒絕策略如下:

1、

AbortPolicy策略

:該策略會直接丟擲異常,阻止系統正常工作;

2、

CallerRunsPolicy策略

:如果執行緒池的執行緒數量達到上限,該策略會把任務佇列中的任務放在呼叫者執行緒當中執行;

3、

DiscardOledestPolicy策略

:該策略會丟棄任務佇列中最老的一個任務,也就是當前任務佇列中最先被新增進去的,馬上要被執行的那個任務,並嘗試再次提交;

4、

DiscardPolicy策略

:該策略會默默丟棄無法處理的任務,不予任何處理。當然使用此策略,業務場景中需允許任務的丟失;

2.4.6 關鍵方法分析

1 private boolean addWorker(Runnable firstTask, boolean core)

此方法用於新增執行緒池的Worker物件。由於一些條件的限制,此方法並不總是能執行成功。具體的執行情況如下:

1 如果當前執行緒池處於STOP、TIDYING和TERMINATED中的任一狀態,不執行新增Worker操作,返回false;

2 以下描述的是執行緒池處於SHUTDOWN狀態的情況:

引數firstTask不為null,也就是說有新任務的提交,不執行新增Worker操作,返回false;

引數firstTask為null,並且當前任務佇列為空,不執行新增Worker操作,返回false;

3 以下描述的是執行緒池處於RUNNING狀態的情況:

執行緒池中Worker的數量已到達能夠容納的最大值CAPACITY,不執行新增Worker操作,返回false;

準備新增的為核心Worker,並且執行緒池中Worker的數量已到達最大值corePoolSize,不執行新增Worker操作,返回false;

準備新增的為非核心Worker,並且執行緒池中Worker的數量已到達最大值 maximumPoolSize,不執行新增Worker操作,返回false。

成功將新建的Worker放入Worker集合後,會啟動Worker物件內部的執行緒,此方法執行是否成功以這個執行緒是否啟動為準,若執行緒啟動的標誌為false,則說明新增Worker的操作失敗,要做新增Worker失敗處理。

addWorkerFailed方法用於處理新增Worker失敗的情況,會執行下面三步操作:

新增Worker邏輯已經建立了一個Worker物件,那麼此方法就要負責從Worker集合中移除這個Worke 物件;

將執行緒池Worker集合大小減1,透過CAS進行操作,自旋直到減成功;

呼叫tryTerminate方法,嘗試停止執行緒池,能否停止成功以tryTerminate的執行為準(嘗試性的停止)。

2 private boolean addWorker(Runnable firstTask, boolean core)

getTask是個比較重要的方法,它負責從任務佇列中獲取任務。

getTask方法有兩種返回資料:

1 null

2 返回null,則預示著執行此方法的Worker將會被從Worker集合中移除;

3 佇列任務

getTask() == null

出現getTask() == null的情況如下:

執行緒池中Worker的總數大於maximumPoolSize;

執行緒池已停止(STOP、TIDYIING和TERMINATEDR任意一種);

執行緒池已關閉(SHUTDOWN),並且任務佇列為空;

執行緒池Worker的總數已經超過corePoolSize或者allowCoreThreadTimeOut設定為true,上一次獲取任務已經超時,當前的Worker不是最後一個或者是最後一個Worker並且任務佇列為空。

3 public void shutdown()

外部透過呼叫shutdown方法來關閉執行緒池,此方法執行的步驟為:

第一步:檢查是否有關閉許可權; 第二步:將執行緒池狀態轉移為SHUTDOWN(迴圈透過CAS更新,直至成功);

第三步:中斷空閒的Worker; 第四步:呼叫onShutdown方法; 第五步:呼叫tryTerminate嘗試終止執行緒池。

4 public List shutdownNow()

外部透過呼叫shutdownNow方法來立即關閉執行緒池,此方法執行的步驟為:

第一步:檢查是否有關閉許可權;

第二步:將執行緒池狀態轉移為STOP(迴圈透過CAS更新,直至成功);

第三步:中斷所有的Worker;

第四步:將任務佇列中的任務轉移出來;

第五步:呼叫tryTerminate嘗試終止執行緒池。

第六步:返回轉移出來的任務列表。

3 簡單用例

3.1 ThreadFactory對執行緒池中建立的執行緒進行記錄與命名

public class ThreadPool { private static ExecutorService pool; public static void main( String[] args ) { //自定義執行緒工廠 pool = new ThreadPoolExecutor(2, 4, 1000, TimeUnit。MILLISECONDS, new ArrayBlockingQueue(5), new ThreadFactory() { public Thread newThread(Runnable r) { System。out。println(“執行緒”+r。hashCode()+“建立”); //執行緒命名 Thread th = new Thread(r,“threadPool”+r。hashCode()); return th; } }, new ThreadPoolExecutor。CallerRunsPolicy()); for(int i=0;i<10;i++) { pool。execute(new ThreadTask()); } }}public class ThreadTask implements Runnable{ public void run() { //輸出執行執行緒的名稱 System。out。println(“ThreadName:”+Thread。currentThread()。getName()); }}

3.2 建立可快取執行緒池CachedThreadPool

//開始下載private void startDownload(final ProgressBar progressBar, final int i) { mCachedThreadPool。execute(new Runnable() { @Override public void run() { int p = 0; progressBar。setMax(10);//每個下載任務10秒 while (p < 10) { p++; progressBar。setProgress(p); Bundle bundle = new Bundle(); Message message = new Message(); bundle。putInt(“p”, p); //把當前執行緒的名字用handler讓textview顯示出來 bundle。putString(“ThreadName”, Thread。currentThread()。getName()); message。what = i; message。setData(bundle); mHandler。sendMessage(message); try { Thread。sleep(1000); } catch (InterruptedException e) { e。printStackTrace(); } } } }); }

4 執行緒會常見的問題

1 使用執行緒池比手動建立執行緒好在哪裡?2 執行緒池的各引數的含義?3 執行緒池有哪 4 種拒絕策略?4 有哪 6 種常見的執行緒池?什麼是 Java8 的 ForkJoinPool?5 執行緒池常用的阻塞佇列有哪些?6 為什麼不應該自動建立執行緒池?7 合適的執行緒數量是多少?CPU 核心數和執行緒數的關係?8 如何根據實際需要,定製自己的執行緒池?9 如何正確關閉執行緒池?shutdown 和 shutdownNow 的區別?10  執行緒池實現“執行緒複用”的原理?

常見出現的問題會在後面的文章討論,想一起討論學習的朋友可以點下

關注,會持續更新

,文章有幫助的話可以

收藏

轉發,

有什麼補充可以在下面

評論,

謝謝!

Top