線程池的使用簡介

線程池的使用:

 

public class ThreadTack extends Thread {

    public static void main(String[] args) {
        threadPoolExecutor();
    }


    /**
     * 1、JDK 自帶線程池
     * 線程池類爲 java.util.concurrent.ThreadPoolExecutor
     * <p>
     * corePoolSize:線程池維護線程的最少數量
     * <p>
     * maximumPoolSize:線程池維護線程的最大數量
     * <p>
     * keepAliveTime:線程池維護線程所允許的空閒時間
     * <p>
     * unit:線程池維護線程所允許的空閒時間的單位
     * <p>
     * workQueue:線程池所使用的緩衝隊列
     * <p>
     * handler:線程池對拒絕任務的處理策略
     * <p>
     * <p>
     * 2、當一個任務通過execute(Runnable)方法欲添加到線程池時:
     * <p>
     * l  如果此時線程池中的數量小於corePoolSize,即使線程池中的線程都處於空閒狀態,也要創建新的線程來處理被添加的任務。
     * <p>
     * l  如果此時線程池中的數量等於 corePoolSize,但是緩衝隊列 workQueue未滿,那麼任務被放入緩衝隊列。
     * <p>
     * l  如果此時線程池中的數量大於等於corePoolSize,緩衝隊列workQueue滿,並且線程池中的數量小於maximumPoolSize,建新的線程來處理被添加的任務。
     * <p>
     * l  如果此時線程池中的數量大於corePoolSize,緩衝隊列workQueue滿,並且線程池中的數量等於maximumPoolSize,那麼通過 handler所指定的策略來處理此任務。
     * <p>
     * 也就是:處理任務的優先級爲:核心線程corePoolSize、任務隊列workQueue、最大線程maximumPoolSize,如果三者都滿了,使用handler處理被拒絕的任務。
     * 一個線程池最多能同時處理的任務數量等於maximumPoolSize + workQueue之和
     * 一個線程池中最多能同時存在的線程數量等於maximumPoolSize
     * <p>
     * l  當線程池中的線程數量大於 corePoolSize時,如果某線程空閒時間超過keepAliveTime,線程將被終止。這樣,線程池可以動態的調整池中的線程數。
     */
    public static void threadPoolExecutor() {
        ThreadPoolExecutor pool = new ThreadPoolExecutor(5, 24, 200, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(6)
                , new ThreadFactoryBuilder().setNameFormat("threadPoolExecutor-example-tools-%d").build());
        for (int i = 0; i < 24; i++) {
            final int num = i;
            pool.execute(() -> {
                System.out.println("正在執行任務:" + num);
                try {
                    Thread.sleep(300000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("任務" + num + "執行完畢");
            });
            System.out.println("線程池中線程數目:" + pool.getPoolSize() + ",隊列中等待執行的任務數目:" +
                    pool.getQueue().size() + ",已執行完別的任務數目:" + pool.getCompletedTaskCount());
        }
    }

    /**
     * Spring自帶線程池
     * 線程池類爲 org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor
     * <p>
     * 一、ThreadPoolExecutor的重要參數
     * 1、corePoolSize:核心線程數
     * * 核心線程會一直存活,及時沒有任務需要執行
     * * 當線程數小於核心線程數時,即使有線程空閒,線程池也會優先創建新線程處理
     * * 設置allowCoreThreadTimeout=true(默認false)時,核心線程會超時關閉
     * <p>
     * 2、queueCapacity:任務隊列容量(阻塞隊列)
     * * 當核心線程數達到最大時,新任務會放在隊列中排隊等待執行
     * <p>
     * 3、maxPoolSize:最大線程數
     * * 當線程數>=corePoolSize,且任務隊列已滿時。線程池會創建新線程來處理任務
     * * 當線程數=maxPoolSize,且任務隊列已滿時,線程池會拒絕處理任務而拋出異常
     * <p>
     * 4、 keepAliveTime:線程空閒時間
     * * 當線程空閒時間達到keepAliveTime時,線程會退出,直到線程數量=corePoolSize
     * * 如果allowCoreThreadTimeout=true,則會直到線程數量=0
     * <p>
     * 5、allowCoreThreadTimeout:允許核心線程超時
     * 6、rejectedExecutionHandler:任務拒絕處理器
     * * 兩種情況會拒絕處理任務:
     * - 當線程數已經達到maxPoolSize,切隊列已滿,會拒絕新任務
     * - 當線程池被調用shutdown()後,會等待線程池裏的任務執行完畢,再shutdown。如果在調用shutdown()和線程池真正shutdown之間提交任務,會拒絕新任務
     * * 線程池會調用rejectedExecutionHandler來處理這個任務。如果沒有設置默認是AbortPolicy,會拋出異常
     * * ThreadPoolExecutor類有幾個內部實現類來處理這類情況:
     * - AbortPolicy 丟棄任務,拋運行時異常
     * - CallerRunsPolicy 執行任務
     * - DiscardPolicy 忽視,什麼都不會發生
     * - DiscardOldestPolicy 從隊列中踢出最先進入隊列(最後一個執行)的任務
     * * 實現RejectedExecutionHandler接口,可自定義處理器
     * <p>
     * 二、ThreadPoolExecutor執行順序
     *  
     * <p>
     *   如果此時線程池中的數量小於corePoolSize,即使線程池中的線程都處於空閒狀態,也要創建新的線程來處理被添加的任務。
     * <p>
     *   如果此時線程池中的數量等於 corePoolSize,但是緩衝隊列 workQueue未滿,那麼任務被放入緩衝隊列。
     * <p>
     *   如果此時線程池中的數量大於等於corePoolSize,緩衝隊列workQueue滿,並且線程池中的數量小於maximumPoolSize,建新的線程來處理被添加的任務。
     * <p>
     *   如果此時線程池中的數量大於corePoolSize,緩衝隊列workQueue滿,並且線程池中的數量等於maximumPoolSize,那麼通過 handler所指定的策略來處理此任務。
     * <p>
     * 也就是:處理任務的優先級爲:核心線程corePoolSize、任務隊列workQueue、最大線程maximumPoolSize,如果三者都滿了,使用handler處理被拒絕的任務。
     * <p>
     *   當線程池中的線程數量大於 corePoolSize時,如果某線程空閒時間超過keepAliveTime,線程將被終止。這樣,線程池可以動態的調整池中的線程數。
     * <p>
     * 三、如何設置參數
     * 1、默認值
     * * corePoolSize=1
     * * queueCapacity=Integer.MAX_VALUE
     * * maxPoolSize=Integer.MAX_VALUE
     * * keepAliveTime=60s
     * * allowCoreThreadTimeout=false
     * * rejectedExecutionHandler=AbortPolicy()
     * <p>
     * 2、如何來設置
     * * 需要根據幾個值來決定
     * - tasks :每秒的任務數,假設爲500~1000
     * - taskcost:每個任務花費時間,假設爲0.1s
     * - responsetime:系統允許容忍的最大響應時間,假設爲1s
     * * 做幾個計算
     * - corePoolSize = 每秒需要多少個線程處理?
     * * threadcount = tasks/(1/taskcost) =tasks*taskcout =  (500~1000)*0.1 = 50~100 個線程。corePoolSize設置應該大於50
     * * 根據8020原則,如果80%的每秒任務數小於800,那麼corePoolSize設置爲80即可
     * - queueCapacity = (coreSizePool/taskcost)*responsetime
     * * 計算可得 queueCapacity = 80/0.1*1 = 80。意思是隊列裏的線程可以等待1s,超過了的需要新開線程來執行
     * * 切記不能設置爲Integer.MAX_VALUE,這樣隊列會很大,線程數只會保持在corePoolSize大小,當任務陡增時,不能新開線程來執行,響應時間會隨之陡增。
     * - maxPoolSize = (max(tasks)- queueCapacity)/(1/taskcost)
     * * 計算可得 maxPoolSize = (1000-80)/10 = 92
     * * (最大任務數-隊列容量)/每個線程每秒處理能力 = 最大線程數
     * - rejectedExecutionHandler:根據具體情況來決定,任務不重要可丟棄,任務重要則要利用一些緩衝機制來處理
     * - keepAliveTime和allowCoreThreadTimeout採用默認通常能滿足
     * <p>
     */
    public static void threadPoolTaskExecutor() {
        ThreadPoolTaskExecutor poolTaskExecutor = new ThreadPoolTaskExecutor();
        //線程池所使用的緩衝隊列
        poolTaskExecutor.setQueueCapacity(200);
        //線程池維護線程的最少數量
        poolTaskExecutor.setCorePoolSize(5);
        //線程池維護線程的最大數量
        poolTaskExecutor.setMaxPoolSize(1000);
        //線程池維護線程所允許的空閒時間
        poolTaskExecutor.setKeepAliveSeconds(30000);
        poolTaskExecutor.setThreadFactory(new ThreadFactoryBuilder().setNameFormat("threadPoolTaskExecutor-example-tools-%d").build());
        poolTaskExecutor.initialize();

        for (int i = 0; i < 14; i++) {
            final int num = i;
            poolTaskExecutor.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println("正在執行任務:" + num);
                    try {
                        Thread.sleep(3000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("任務" + num + "執行完畢");
                }
            });
            ThreadPoolExecutor threadPoolExecutor = poolTaskExecutor.getThreadPoolExecutor();
            System.out.println("線程池中線程數目:" + threadPoolExecutor.getPoolSize() + ",隊列中等待執行的任務數目:" +
                    threadPoolExecutor.getQueue().size() + ",已執行完別的任務數目:" + threadPoolExecutor.getCompletedTaskCount());
        }
    }


    /**
     * 1、JDK 自帶線程池
     * 線程池類爲 java.util.concurrent.Executors
     * <p>
     * 創建一個可緩存線程池,如果線程池長度超過處理需要,可靈活回收空閒線程,若無可回收,則新建線程。
     * <p>
     * 如果線程池的大小超過了處理任務所需要的線程,那麼就會回收部分空閒(60秒不執行任務)的線程,當任務數增加時,此線程池又可以智能的添加新線程來處理任務。
     * 此線程池不會對線程池大小做限制,線程池大小完全依賴於操作系統(或者說JVM)能夠創建的最大線程大小。
     * 缺點:大家一般不用是因爲newCachedThreadPool 可以無線的新建線程,容易造成堆外內存溢出,因爲它的最大值是在初始化的時候設置爲 Integer.MAX_VALUE,一般來說機器都沒那麼大內存給它不斷使用。當然知道可能出問題的點,就可以去重寫一個方法限制一下這個最大值,但是出於後期維護原因,一般來說用 newFixedThreadPool 也就足夠了。
     */
    static void newCachedThreadPool() {
        ExecutorService cachedThreadPool = Executors.newCachedThreadPool(new ThreadFactoryBuilder().setNameFormat("newCachedThreadPool-example-tools-%d").build());
//        new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS,new SynchronousQueue<Runnable>());
        for (int i = 0; i < 10; i++) {

            final int index = i;

            try {

                Thread.sleep(index * 1000);

            } catch (InterruptedException e) {

                e.printStackTrace();

            }

            cachedThreadPool.execute(new Runnable() {
                @Override
                public void run() {

                    System.out.println(index);
                }
            });
        }
    }

    /**
     * 創建一個定長線程池,可控制線程最大併發數,超出的線程會在隊列中等待
     * <p>
     * 創建一個定長線程池,可控制線程最大併發數,超出的線程會在隊列中等待。每次提交一個任務就創建一個線程,直到線程達到線程池的最大大小。
     * 線程池的大小一旦達到最大值就會保持不變,如果某個線程因爲執行異常而結束,那麼線程池會補充一個新線程。
     * <p>
     * 因爲線程池大小爲3,每個任務輸出index後sleep 2秒,所以每兩秒打印3個數字。
     * 其實newFixedThreadPool()在嚴格上說並不會複用線程,每運行一個Runnable都會通過ThreadFactory創建一個線程。
     */
    static void newFixedThreadPool() {
        ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3, new ThreadFactoryBuilder().setNameFormat("newFixedThreadPool-example-tools-%d").build());
        new ThreadPoolExecutor(2, 3, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>());
        for (int i = 0; i < 10; i++) {
            final int index = i;

            fixedThreadPool.execute(new Runnable() {
                @Override
                public void run() {
                    try {
                        System.out.println(index);

                        Thread.sleep(2000);

                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });
        }
    }

    /**
     * 創建一個定長線程池,支持定時及週期性任務執行
     * <p>
     * ScheduledThreadPoolExecutor的設計思想是,每一個被調度的任務都會由線程池中一個線程去執行,因此任務是併發執行的,相互之間不會受到干擾。
     * 需要注意的是,只有當任務的執行時間到來時,ScheduedExecutor 纔會真正啓動一個線程,其餘時間 ScheduledExecutor 都是在輪詢任務的狀態。
     */
    static void newScheduledThreadPool() {
        ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5, new ThreadFactoryBuilder().setNameFormat("newScheduledThreadPool-example-tools-%d").build());
        for (int i = 0; i < 10; i++) {
            final int index = i;

            scheduledThreadPool.schedule(new Runnable() {
                @Override
                public void run() {
                    System.out.println("delay 3 seconds" + index);

                }
            }, 3, TimeUnit.SECONDS);
        }
    }

    /**
     * 表示延遲1秒後每3秒執行一次。
     * 與Timer 對比:
     * <p>
     * Timer 的優點在於簡單易用,但由於所有任務都是由同一個線程來調度,因此所有任務都是串行執行的,同一時間只能有一個任務在執行,
     * 前一個任務的延遲或異常都將會影響到之後的任務(比如:一個任務出錯,以後的任務都無法繼續)。
     * <p>
     * ScheduledThreadPoolExecutor的設計思想是,每一個被調度的任務都會由線程池中一個線程去執行,因此任務是併發執行的,相互之間不會受到干擾。
     * 需要注意的是,只有當任務的執行時間到來時,ScheduedExecutor 纔會真正啓動一個線程,其餘時間 ScheduledExecutor 都是在輪詢任務的狀態。
     * <p>
     * 通過對比可以發現ScheduledExecutorService比Timer更安全,功能更強大,在以後的開發中儘可能使用ScheduledExecutorService(JDK1.5以後)替代Timer
     */
    static void newAtFixedRate() {
        ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5, new ThreadFactoryBuilder().setNameFormat("newAtFixedRate-example-tools-%d").build());
//        new ThreadPoolExecutor(1, 1,0L,TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>());
        scheduledThreadPool.scheduleAtFixedRate(new Runnable() {
            @Override
            public void run() {
                System.out.println("delay 1 seconds, and excute every 3 seconds");
            }

        }, 1, 3, TimeUnit.SECONDS);
    }

    /**
     * 創建一個單線程的線程池。這個線程池只有一個線程在工作,也就是相當於單線程串行執行所有任務。如果這個唯一的線程因爲異常結束,那麼會有一個新的線程來替代它。
     * 保證所有任務按照指定順序(FIFO, LIFO, 優先級)執行。
     */
    static void newSingleThreadExecutor() {
        ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor(new ThreadFactoryBuilder().setNameFormat("newSingleThreadExecutor-example-tools-%d").build());

        for (int i = 0; i < 10; i++) {
            final int index = i;

            singleThreadExecutor.execute(new Runnable() {
                @Override
                public void run() {
                    try {
                        System.out.println(index);

                        Thread.sleep(2000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });
        }
    }


    //寫一個類執行十次隨機時間休眠
    @Override
    public void run() {
        try {
            for (int i = 0; i < 10; i++) {
                int time = (int) (Math.random() * 1000);
                Thread.sleep(time);
                System.out.println("當前線程是  " + Thread.currentThread().getName());
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    /**
     * new Thread的弊端如下:
     * <p>
     * a. 每次new Thread新建對象性能差。
     * b. 線程缺乏統一管理,可能無限制新建線程,相互之間競爭,及可能佔用過多系統資源導致死機或oom。
     * c. 缺乏更多功能,如定時執行、定期執行、線程中斷。
     * 相比new Thread,Java提供的四種線程池的好處在於:
     * a. 重用存在的線程,減少對象創建、消亡的開銷,性能佳。
     * b. 可有效控制最大併發線程數,提高系統資源的使用率,同時避免過多資源競爭,避免堵塞。
     * c. 提供定時執行、定期執行、單線程、併發數控制等功能。
     */
    static void newThread() {
        try {
            ThreadTack thread = new ThreadTack();
            thread.setName("new Thread");
            thread.start();
            for (int i = 0; i < 10; i++) {
                final int index = i;
                int time = (int) (Math.random() * 1000);
                Thread.sleep(time);
                System.out.println("當前線程是  " + Thread.currentThread().getName());
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }


}

       項目中涉及到了線程池的使用,最初使用的是Executors的工廠模式向我們提供了4種線程池實現方式,但是阿里的編碼規約並不推薦使用,原因是使用Executors創建線程池不會傳入這個參數而使用默認值所以我們常常忽略這一參數,而且默認使用的參數會導致資源浪費,不可取。

阿里的 Java開發手冊,上面有線程池的一個建議:

【強制】線程池不允許使用 Executors 去創建,而是通過 ThreadPoolExecutor 的方式,
這樣的處理方式讓寫的同學更加明確線程池的運行規則,規避資源耗盡的風險。

ThreadPoolExecutor

先看看如何使用ThreadPoolExecutor創建線程池:

    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) 

下面來解釋下各個參數:

  • int corePoolSize:該線程池中核心線程數最大值

核心線程:線程池新建線程的時候,如果當前線程總數小於corePoolSize,則新建的是核心線程,如果超過corePoolSize,則新建的是非核心線程核心線程默認情況下會一直存活在線程池中,即使這個核心線程啥也不幹(閒置狀態)。
如果指定ThreadPoolExecutor的allowCoreThreadTimeOut這個屬性爲true,那麼核心線程如果不幹活(閒置狀態)的話,超過一定時間(時長下面參數決定),就會被銷燬掉。

  • int maximumPoolSize: 該線程池中線程總數最大值

線程總數 = 核心線程數 + 非核心線程數。

  • long keepAliveTime:該線程池中非核心線程閒置超時時長

一個非核心線程,如果不幹活(閒置狀態)的時長超過這個參數所設定的時長,就會被銷燬掉,如果設置allowCoreThreadTimeOut = true,則會作用於核心線程。

  • TimeUnit unit:keepAliveTime的單位

TimeUnit是一個枚舉類型,其包括:
NANOSECONDS : 1微毫秒 = 1微秒 / 1000
MICROSECONDS : 1微秒 = 1毫秒 / 1000
MILLISECONDS : 1毫秒 = 1秒 /1000
SECONDS : 秒
MINUTES : 分
HOURS : 小時
DAYS : 天

  • BlockingQueue workQueue:該線程池中的任務隊列:維護着等待執行的Runnable對象

當所有的核心線程都在幹活時,新添加的任務會被添加到這個隊列中等待處理,如果隊列滿了,則新建非核心線程執行任務。
常用的workQueue類型:

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

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

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

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

  • ThreadFactory threadFactory:創建線程的方式,這是一個接口,你new他的時候需要實現他的Thread newThread(Runnable r)方法,一般用不上。

  • RejectedExecutionHandler handler:這玩意兒就是拋出異常專用的,比如上面提到的兩個錯誤發生了,就會由這個handler拋出異常,根本用不上。

二、向ThreadPoolExecutor添加任務

我們怎麼知道new一個ThreadPoolExecutor,大概知道各個參數是幹嘛的,可是我new完了,怎麼向線程池提交一個要執行的任務啊?

ThreadPoolExecutor.execute(Runnable command)

通過ThreadPoolExecutor.execute(Runnable command)方法即可向線程池內添加一個任務。

三、 明確拒絕策略

ThreadPoolExecutor.AbortPolicy: 丟棄任務並拋出RejectedExecutionException異常。 (默認)
ThreadPoolExecutor.DiscardPolicy:也是丟棄任務,但是不拋出異常。
ThreadPoolExecutor.DiscardOldestPolicy:丟棄隊列最前面的任務,然後重新嘗試執行任務(重複此過程)
ThreadPoolExecutor.CallerRunsPolicy:由調用線程處理該任務

說明:Executors 各個方法的弊端:
1)newFixedThreadPool 和 newSingleThreadExecutor:
主要問題是堆積的請求處理隊列可能會耗費非常大的內存,甚至 OOM。
2)newCachedThreadPool 和 newScheduledThreadPool:
主要問題是線程數最大數是 Integer.MAX_VALUE,可能會創建數量非常多的線程,甚至 OOM。

這裏給總結一下,當一個任務被添加進線程池時,執行策略:

  • 1.線程數量未達到corePoolSize,則新建一個線程(核心線程)執行任務
  • 2.線程數量達到了corePools,則將任務移入隊列等待
  • 3.隊列已滿,新建線程(非核心線程)執行任務
  • 4.隊列已滿,總線程數又達到了maximumPoolSize,就會由(RejectedExecutionHandler)拋出異常

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章