P1126 機器人搬重物
參考:https://www.cnblogs.com/jiawen010/p/11855768.html
一、執行緒池簡介
1. 執行緒池的概念
執行緒池就是首先建立一些執行緒,它們的集合稱為執行緒池。使用執行緒池可以很好的提高效能,執行緒池在系統啟動時即建立大量空閒的執行緒,程式將一個任務傳給執行緒池,執行緒池就會啟動一條執行緒來執行這個任務,執行結束後,該執行緒並不會死亡,而是再次返回執行緒池中,成為空閒狀態,等待執行下一個任務。
2. 執行緒的工作機制
線上程池的程式設計模式下,任務是提交給整個執行緒池,而不是直接提交給某個執行緒,執行緒池在拿到任務後,就在內部尋找是否有空閒的執行緒,如果有,就把任務交給某個空間的執行緒。
一個執行緒同時只能執行一個任務,但可以同時向一個執行緒池提交多個任務。
3. 使用執行緒池的原因
- 多執行緒執行時,系統不斷的建立和關閉新執行緒,成本非常高,會過度消耗系統資源
- 提高系統響應速度,當有任務到達時,通過複用已有的執行緒,無需等待新執行緒的建立就能立即執行
- 方便執行緒併發數的管控。因為執行緒若是無限制的建立,可能會導致記憶體佔用過多而產生 OOM,並且會造成 cpu 過度切換,影響系統性能
二、執行緒池主要引數
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) { this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, Executors.defaultThreadFactory(), defaultHandler); }
1. corePoolSize(執行緒池基本大小):當向執行緒池提交一個任務時,若執行緒池已建立的執行緒數小於 corePoolSize,即便此時存在空閒執行緒,也會通過建立一個新執行緒來執行該任務,直到已建立的執行緒數大於或等於 corePoolSize 時。除了利用提交新任務來建立和啟動執行緒(按需構造),也可以通過 prestartCoreThread() 或 prestartAllCoreThreads() 方法來提前啟動執行緒池中的基本執行緒。
2. maximumPoolSize(執行緒池最大大小):執行緒池所允許的最大執行緒個數。當佇列滿了,且已建立的執行緒數小於 maximumPoolSize,則執行緒池會建立新的執行緒來執行任務。另外,對於無界佇列,可忽略此引數。
3. keepAliveTime(執行緒存活保持時間):當執行緒池中執行緒數大於核心執行緒數時,執行緒的空閒時間如果超過執行緒存活時間,那麼這個執行緒就會被銷燬,直到執行緒池中的執行緒數小於等於核心執行緒數。
4. workQueue(任務佇列):用於傳輸和儲存等待執行任務的阻塞佇列。
5. threadFactory(執行緒工廠):用於建立新執行緒。threadFactory 建立的執行緒也是採用 new Thread() 方式,threadFactory 建立的執行緒名都有統一的風格:pool-m-thread-n (m 為執行緒池的編號, n 為執行緒池內執行緒編號)。
6. handler(執行緒飽和策略):當執行緒池和佇列都滿了,再加入執行緒會執行此策略。
執行緒池流程:判斷核心執行緒池是否已滿,沒滿則建立一個新的工作執行緒來執行任務,已滿則判斷任務佇列(等待執行緒執行的任務的佇列)是否已滿,沒滿則將新提交的任務新增在工作佇列,已滿則判斷整個執行緒池是否已滿,沒滿則建立一個新的工作執行緒來執行任務,已滿則執行飽和策略。
分別判斷:核心執行緒池 -> 工作佇列 -> 整個執行緒池
三、執行緒池為什麼要使用阻塞佇列而不使用非阻塞佇列?
阻塞佇列可以保證任務佇列中沒有任務時阻塞獲取任務的執行緒,使得執行緒進入 wait 狀態,釋放 CPU 資源。
當佇列中有任務時,才喚醒對應執行緒從佇列中取出訊息進行執行。
使得執行緒不至於一直佔用 cpu 資源。
四、如何配置執行緒池
CPU 密集型任務:儘量使用較小的執行緒池,一般為 CPU 核心數 +1。因為 CPU 密集型任務使得 CPU 使用率很高,若開過多的執行緒數,會造成 CPU 過度切換。
IO 密集型任務:可以使用稍大的執行緒池,一般為 2*CPU 核心數。IO 密集型任務 CPU 使用率並不高,因此可以讓 CPU 在等待 IO 的時候由其他執行緒去處理別的任務,充分利用 CPU 時間。
混合型任務:可以將任務分成 IO 密集型和 CPU 密集型任務,然後分別用不同的執行緒池去處理。只要分完之後兩個任務的執行時間相差不大,那麼就會比序列執行來的高效。因為如果劃分後兩個任務執行時間有數量級的差距,那麼拆分沒有意義。先執行完的任務要等後執行完的任務,最終的時間仍取決於後執行完的任務,而且還要加上任務拆分與合併的開銷,得不償失。(並不很理解)
五、四種常見的執行緒池詳解
執行緒池的返回值 ExecutorService 是 Java 提供的用於管理執行緒池的類。該類有兩個作用:控制執行緒數量和重用執行緒
1. Executors.newCacheThreadPool():可快取執行緒池,先檢視池中有沒有以前建立的執行緒,如果有,就直接使用,如果沒有,就建一個新的執行緒加入池中,快取型池子通常用於執行一些生存期很短的非同步型任務。
public class Test { public static void main(String[] args) { ExecutorService cachedThreadPool = Executors.newCachedThreadPool(); for(int i=0; i<10; i++){ try{ Thread.sleep(1000); }catch (InterruptedException e){ e.printStackTrace(); } cachedThreadPool.execute(new Runnable() { @Override public void run() { System.out.println(Thread.currentThread().getName() + " 正在被執行 "); try { Thread.sleep(2000); }catch (InterruptedException e){ e.printStackTrace(); } } }); } } }
這個執行緒池為無限大,當執行當前任務時,上一個任務已完成,會複用執行上一個任務的執行緒,而不是每次新建執行緒。
感覺有點問題,在任務執行特別多速度特別慢的時候,這裡的執行緒池中執行緒數量就會急劇上升
2. Executors.newFixedThreadPool(int i):建立一個可重用固定個數的執行緒池,以共享的無界佇列方式來執行這些執行緒。
public static void main(String[] args) { ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3); for(int i=0; i<10; i++){ fixedThreadPool.execute(new Runnable() { @Override public void run() { System.out.println(Thread.currentThread().getName() + " 正在被執行"); try{ Thread.sleep(2000); } catch(InterruptedException e){ e.printStackTrace(); } } }); } }
因為執行緒池大小為 3,每個任務輸出列印結果後 sleep 2秒,所以每兩秒列印 3 個結果。
定長執行緒池的大小最好根據系統資源進行設定。如 Runtime.getRuntime().availableProcessors()。
3. Executors.newScheduledThreadPool(int n):建立一個定長執行緒池,支援定時及週期性任務執行
public static void main(String[] args) { ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5); scheduledThreadPool.scheduleAtFixedRate(new Runnable() { @Override public void run() { System.out.println("延遲 1 秒後每 3 秒執行一次"); } }, 1, 3, TimeUnit.SECONDS); }
4. Executors.newSingleThreadExecutor():建立一個單執行緒化的執行緒池,它只會用唯一的工作執行緒來執行任務,保證所有的任務按照指定順序(FIFO,LIFO,優先順序)執行。
public static void main(String[] args) { ExecutorService singleThreadPool = Executors.newSingleThreadExecutor(); for(int i=0; i<10; i++){ final int index = i; singleThreadPool.execute(new Runnable() { @Override public void run() { try{ System.out.println(Thread.currentThread().getName() + " 正在執行,列印的值是:" + index); Thread.sleep(500); }catch (InterruptedException e){ e.printStackTrace(); } } }); } }
六、緩衝佇列 BlockingQueue 和自定義執行緒池 ThreadPoolExecutor
1. 緩衝佇列 BlockingQueue 簡介
BlockingQueue 是雙緩衝佇列。BlockingQueue 內部使用兩條佇列,允許兩個執行緒同時向列隊 一個儲存一個取出操作。在保證併發安全的同時,提高了佇列的存取效率。
2. 常用的幾種 BlockingQueue
- ArrayBlockingQueue(int i):規定大小的 BlockingQueue,其構造必須指定大小。其所含的物件是 FIFO 順序排序的
- LinkedBlockingQueue() 或者 (int i):大小不固定的 BlockingQueue,如果其構造時指定大小,生成的 BlockingQueue 有大小限制,不指定大小,其大小由 Integer.MAX_VALUE 來決定。其所含的物件是 FIFO 順序排序的
- PriorityBlockingQueue() 或者 (int i):類似於 LinkedBlockingQueue,但是其所含物件的排序不是 FIFO,而是依據物件的自然順序或者建構函式的 Comparator 決定
- SynchronizedQueue():特殊的 BlockingQueue,對其的操作必須是放和取的交替完成
3. 自定義執行緒池(ThreadPoolExecutor 和 BlockingQueue 連用)
自定義執行緒池,可以用 ThreadPoolExecutor 來建立,它有多個構造方法來建立執行緒池
public class MyThreadPoolExecutor { public static class Temphread implements Runnable { @Override public void run() { System.out.println(Thread.currentThread().getName() + " 正在執行"); try{ Thread.sleep(1000); }catch (InterruptedException e){ e.printStackTrace(); } } } public static void main(String[] args) { BlockingQueue<Runnable> bq = new ArrayBlockingQueue<>(10); ThreadPoolExecutor executor = new ThreadPoolExecutor(3, 6, 50, TimeUnit.SECONDS, bq); Runnable t1 = new Temphread(); Runnable t2 = new Temphread(); Runnable t3 = new Temphread(); Runnable t4 = new Temphread(); Runnable t5 = new Temphread(); Runnable t6 = new Temphread(); executor.execute(t1); executor.execute(t2); executor.execute(t3); executor.execute(t4); executor.execute(t5); executor.execute(t6); executor.shutdown(); } }