--- title: 'Java 線程池 / 執行序池' disqus: kyleAlien --- Java 線程池 / 執行序池 === ## OverView of Content 如有引用參考請詳註出處,感謝 :smile: > 阻塞線程使用資料結構 [**Queue**](https://hackmd.io/oE5XHO3NThCb1Grn7foPMA) 收發任務 [TOC] ## 認識執行序池 **Java 的執行序池**(`Thread Pool`,也稱之為線程池)是開發多執行序(`multithrad`)時最常使用到的微框架,**我們可以基於不同開發要求創建不同特性的執行序池**… 這也是我們接下來會介紹的 首先我們先來了解執行序池帶給開發者的幾個好處,為什麼不直接創建執行緒,而要用執行緒池 1. **降低資源消耗**:因為執行緒的 **創建、銷毀都需要資源的消耗** 2. **降低時間的消耗,達到更快的響應速度**:執行序池只有在創造時、最後銷毀回收時才需要消耗時間,而對於已經創建的執行序會持續 Cache,執行系統回收執行緒池物件,透過執行緒 instance 的反覆利用來達到高響應速度 :::warning 當然有好就有壞,對於執行緒池 Cache 執行緒的數量若是沒有管控好,也會造成佔據記憶體空間,導致記憶體抖動(內存抖動) ::: 3. **提高可管理性:==執行序是稀缺資源==**,Linux、Window、Mac 對於執行序的創造數量都有個別的限制; 這裡我們大概了解一下每個系統執行序的上限(但根據系統的版本、升級執行序的上限都可能改變) * Linux : 5000 個執行序 * Window : 2000 個執行序 * Mac : 1000 個執行序 ## ThreadPoolExecutor 參數 * 從 ThreadPoolExecutor 介紹,一般使用都是呼叫 Executors 類的靜態方法,但是 Executors 也是包裝過 ThreadPoolExecutor 來實現不同特性的執行序池 ### UML > Java 執行序池 UML 關係圖 >  > > [**Executor**](https://developer.android.com/reference/java/util/concurrent/Executor.html) : 解偶`任務提交` & `調度細節` > > 一般使用提交任務是用 : new Thread(new Runnable()); 每個任務都由使用者親自調用 Thread 才能執行 > > 使用 Executor,Thread 調度的部分就可以被隔離開(在內部 new Thread 並加上其他細節) ```java= Executor e = new Executor(); e.execute(new Runnable); ``` * [**ExecutorService**](https://developer.android.com/reference/java/util/concurrent/ExecutorService.html) : 提供`追蹤異步線程` Futrue 的分法 & 管理`終止` > submit 接收參數 [**Callable**] > (https://hackmd.io/4itn0slyRMWe8B5Q0s8xKQ#Callable) >  > shutDown 關閉執行序池 >  * [**AbstractExecutorService**](https://developer.android.com/reference/java/util/concurrent/AbstractExecutorService.html) : 預設實現 [**RunnableFuture**](https://developer.android.com/reference/java/util/concurrent/RunnableFuture.html) 並返回 > 內部(被繼承時 protect 用來複寫,並不對外開放) 使用 **newTaskFor 來實現 Runnable & Callable** 這兩種不同的界面 >  * [**ThreadPoolExecutor**](https://developer.android.com/reference/java/util/concurrent/ThreadPoolExecutor?hl=en) : Executors factory methods,**執行序池實現的工廠**,參數下面會介紹 >  * [**Executors**](https://developer.android.com/reference/java/util/concurrent/Executors.html?hl=en#newScheduledThreadPool(int)) : 使用 ThreadPoolExecutor 創造不同特性的執行序池 >  ### 核心量 * 每當 User 提交一個任務到執行序池,它就創造一個 Thread,**直到提交到了核心上限** * 所以一開始它是收到一個任務創建一個 Thread,但**可以透過 prestartAllCoreThreads() 這個方法提前創建**規定數量範圍內的 Thread ### 最大上限 * 當隊列滿了後,**再接收到任務則創建 Thread(類似打工仔)**,此參數規定創建的上限 ### 存活時間 * 打工仔 Thread 的存活時間,**該參數在 coreThread 小於 workThread 時才有用** ### 時間單位 * 存活時間的時間單位 [**Android TimeUnit**](https://developer.android.com/reference/kotlin/java/util/concurrent/TimeUnit?hl=en#enum-values_1) ### 隊列 * workQueue 必須是 BlockingQueue 阻塞隊列,它會**將超出 core 能處理的數量的任務塞入隊列(FIFO)**, ### 拒絕策略 * 當隊列滿,並且 workThread 也滿時就會啟動拒絕策略 (類似滿載),決定如何處理多出的任務 > 1. **AbortPolicy 默認拒絕策略**,直接拋出異常 > 2. CallerRunsPolicy 讓調用方法的線程自己處理,可以減緩任務提交速度 > 3. DiscardPolicy 不執行任務,直接拋棄 > 4. DiscardOldestPolicy 丟棄最老舊的任務,並執行當前任務 ### 順序圖 >  ### [ThreadFactory](https://developer.android.com/reference/java/util/concurrent/ThreadFactory) 界面 * 創建線程的工廠,處理線程的細節 * **隔離出 Thread 的功能只對外開放自己創出的 Func** ```java= public class ThreadFactoryTest { public static void main(String[] args) { TestThreadFactory t = new TestThreadFactory(); for(int i = 0; i < 10; i++) { t.newThread(new Runnable() { @Override public void run() { //System.out.println("Thread Factory Working"); } }).start(); System.out.println("Thread Factory Name: " + t.getThreadName()); } } } class TestThreadFactory implements ThreadFactory { private int count; private Thread t; TestThreadFactory() { count = 0; } @Override public Thread newThread(Runnable arg0) { String tName = "Thread-" + count + "-make"; t = new Thread(arg0, tName); count++; return t; } public String getThreadName() { return t == null ? null : t.getName(); } } ``` **--實作--** > 可看出**外部無法取得線程名子**,只能拿取該類有的方法 >  ## 執行序池 * **線程對於操作系統來說是一個稀缺資源**,資源的建立、釋放都需要耗費一定的性能,所以創建一個固定數量的線成就可以省去不斷的創建線程所耗費的性能 (野線程也不易管理) > ### 執行序池工具 [Executors](https://developer.android.com/reference/kotlin/java/util/concurrent/Executors) 1. ==FixedThreadPool== 創建固定 Thread > 只有核心線程,0 workThread,workThread 存活時間為 0s,並使用 LinkedBlockingQueue > ![] 2. ==newCachedThreadPool== 快速緩存 Thread > 0個核心線程,只有 workThread,workThread 存活時間為 60s,並使用,並使用 SynchronusQueue (不儲存任務) >  3. ==SingleThreadExexutor== 單執行 Thread > 1個核心線程,0 workThread,workThread 存活時間為 0s,並使用 LinkedBlockingQueue > **可確保所有任務依照順序執行** >  4. ==ScheduledThreadPoolExecutor== 定期執行 Thread > n 個核心線程,無界 workThread,workThread 存活時間為 10ms,並使用 DelayWorkQueue,任務的創建、取用是從 DelayWorkQueue,它也會自己排序要出來的順序 >  ### 執行序池工作機制 1. 如果當前線程數量不足,並且任務數量大於 corePoolSize,則當前線程會創建線程到 corePoolSize 的數量 (**注意※這一行為需要全局鎖**) 2. 如果處理任務數量 > corePoolSize,則將任務存入 BlockingQueue 佇列中排隊 3. 假如 BlockingQueue 也滿了,會創建新的線程(打工仔線程) 4. 當前線程如果超出數量則掉用具則策略,RejectedExcutionHandler.rejectedExecution 方法 >  ### 關閉執行序池 * 關閉的方式分為兩種,shutdown、shutdownNow 這兩種方法都是依序對於隊列中的任務發出中斷訊號,==**如果任務中沒有特別處理中斷訊號則可能永遠無法停止**== * 要調用哪一種方法決定於任務的重要性 | 方法 | 功能 | 注意 | | -------- | -------- | -------- | | shutdown | 停止當前執行序池(Stop) | 只中斷沒有在運行的任務,當前任務仍然執行 | | shutdownNow | 狀態切換程(Shutdown) | 包括正在執行的任務 | ### 合理配置執行序池數量 * 可從以下角度分析 1. **性質** : CPU 集中型、IO 集中型、混和型 2. **優先級** : 高、中、低 3. **執行時間** : 長、中、短 4. **依賴性** : 是否依賴其他資源 > 當前 CPU 核心數量 > > N = Runtime.getRuntime().availableProcessors(); > > CPU 集中型 : 分配 N + 1 (+1 考慮到的是虛擬內存的部份,不要讓其有空線程) > IO 集中型 : 分配 N\*2 + 1 :::warning 分配的數量在於 newThreadPoolExecutor Max 這個參數,**控制最大上限的線程數量** ::: ## AsyncTask 分析 ### 基礎使用 * 重點方法 1. **==onPreExecute()==** : **執行前準備** >  2. **==doInBackground(Params... prarms)==** : 在執行序池中執行,可調用 publishProgress(Progress... value) 更新 UI,但**此方法不能更新 UI**,只能接收後端處理資料的進度 >  3. **==onProgressUpdate(Params... prarms)==** : 在主執行序執行,當調用 publishProgress(Progress... value) 此方法可**更新 UI** 上 >  4. **==onPostExecute(Result result)==** : 在主執行序執行,**做結尾**的部分 >  * 執行只需調用 **execute()** 方法即可 >  ### 源碼分析 * AsyncTask 是一個抽象泛型類 > <Params, Progress, Result\> > Params: 要處理的類 > Progress: 處理過程返回的類 > Result: Return value, let Callable use it * 核心執行序數量: 最少兩個最多 4 個依照 CPU 執行序數量規定 > CPU 核心數量 = Runtime.getRuntime().availableProcessors(); > Pool 核心數量 = Math.max(2, Math.min(CPU_COUNT - 1, 4)); > Pool 子數量 = CPU_COUNT * 2 + 1 > Work Thread a life time = 30 (s) > 使用`有界` Linked 堵塞任務 = LinkedBlockingQueue<Runnable\> > > **Thread Factory 做了原子操做** (AtomicInteger) 替執行序取名 > >  * 使用類加載機制,靜態變量創出 ThreadPoolExecutor,再設定到 static final 變量,並設定 Core Thread 也可關閉 >  * 建構函數中使用了兩個暱名內部類(可省略類的創建,抽象類界面都可內名抽象) > 匿名創建了兩個類: > > **FutureTask 類**,並覆寫 **done()** 方法 > > 內部抽象類 `WorkerRunnable<Params, Result>`,覆寫 Callable 界面的 **call()** 方法 > >  ### 時序圖 >  ## Appendix & FAQ :::info ::: ###### tags: `Java 基礎進階`
×
Sign in
Email
Password
Forgot password
or
By clicking below, you agree to our
terms of service
.
Sign in via Facebook
Sign in via Twitter
Sign in via GitHub
Sign in via Dropbox
Sign in with Wallet
Wallet (
)
Connect another wallet
New to HackMD?
Sign up