1. 程式人生 > 實用技巧 >P1126 機器人搬重物

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();

    }
}