1. 程式人生 > >Java線程池的配置

Java線程池的配置

runnable web 支持 mar val .exe 什麽 rdp throw

1、ThreadPoolExecutor的重要參數

 1corePoolSize:核心線程數
        * 核心線程會一直存活,及時沒有任務需要執行
        * 當線程數小於核心線程數時,即使有線程空閑,線程池也會優先創建新線程處理
        * 設置allowCoreThreadTimeout=true(默認false)時,核心線程會超時關閉

    2queueCapacity:任務隊列容量(阻塞隊列)
        * 當核心線程數達到最大時,新任務會放在隊列中排隊等待執行

    3maxPoolSize:最大線程數
        * 當線程數>=corePoolSize,且任務隊列已滿時。線程池會創建新線程來處理任務
        * 當線程數=maxPoolSize,且任務隊列已滿時,線程池會拒絕處理任務而拋出異常

    4 keepAliveTime:線程空閑時間
        * 當線程空閑時間達到keepAliveTime時,線程會退出,直到線程數量=corePoolSize
        * 如果allowCoreThreadTimeout=true,則會直到線程數量=0

    5allowCoreThreadTimeout:允許核心線程超時
    6rejectedExecutionHandler:任務拒絕處理器
        * 兩種情況會拒絕處理任務:
            - 當線程數已經達到maxPoolSize,切隊列已滿,會拒絕新任務
            - 當線程池被調用shutdown()後,會等待線程池裏的任務執行完畢,再shutdown。如果在調用shutdown()和線程池真正shutdown之間提交任務,會拒絕新任務
        * 線程池會調用rejectedExecutionHandler來處理這個任務。如果沒有設置默認是AbortPolicy,會拋出異常
        * ThreadPoolExecutor類有幾個內部實現類來處理這類情況:
            - AbortPolicy 丟棄任務,拋運行時異常
            - CallerRunsPolicy 執行任務
            - DiscardPolicy 忽視,什麽都不會發生
            - DiscardOldestPolicy 從隊列中踢出最先進入隊列(最後一個執行)的任務
        * 實現RejectedExecutionHandler接口,可自定義處理器

2、線程池隊列的選擇

wordQueue任務隊列,用於轉移和阻塞提交了的任務,即任務隊列是運行線程的,任務隊列根據corePoolSize和maximumPoolSize工作:

1.當正在運行的線程小於corePoolSize,線程池會創建新的線程

2.當大於corePoolSize而任務隊列未滿時,就會將整個任務塞入隊列

3.當大於corePoolSize而且任務隊列滿時,並且小於maximumPoolSize時,就會創建新額線程執行任務

4、當大於maximumPoolSize時,會根據handler策略處理線程

任務隊列有以下三種模式:

1. 直接提交。工作隊列的默認選項是 SynchronousQueue

,它將任務直接提交給線程而不保持它們。在此,如果不存在可用於立即運行任務的線程,則試圖把任務加入隊列將失敗,因此會構造一個新的線程。此策略可以避免在處理可能具有內部依賴性的請求集時出現鎖。直接提交通常要求無界 maximumPoolSizes 以避免拒絕新提交的任務。當命令以超過隊列所能處理的平均數連續到達時,此策略允許無界線程具有增長的可能性。

2.無界隊列。使用無界隊列(例如,不具有預定義容量的 LinkedBlockingQueue)將導致在所有 corePoolSize 線程都忙時新任務在隊列中等待。這樣,創建的線程就不會超過 corePoolSize。(因此,maximumPoolSize 的值也就無效了。)當每個任務完全獨立於其他任務,即任務執行互不影響時,適合於使用無界隊列;例如,在 Web 頁服務器中。這種排隊可用於處理瞬態突發請求,當命令以超過隊列所能處理的平均數連續到達時,此策略允許無界線程具有增長的可能性。

3.有界隊列。當使用有限的 maximumPoolSizes 時,有界隊列(如 ArrayBlockingQueue)有助於防止資源耗盡,但是可能較難調整和控制。隊列大小和最大池大小可能需要相互折衷:使用大型隊列和小型池可以最大限度地降低 CPU 使用率、操作系統資源和上下文切換開銷,但是可能導致人工降低吞吐量。如果任務頻繁阻塞(例如,如果它們是 I/O 邊界),則系統可能為超過您許可的更多線程安排時間。使用小型隊列通常要求較大的池大小,CPU 使用率較高,但是可能遇到不可接受的調度開銷,這樣也會降低吞吐量。

3、線程池的飽和策略

1.在默認的 ThreadPoolExecutor.AbortPolicy 中,處理程序遭到拒絕將拋出運行時 RejectedExecutionException

2.在 ThreadPoolExecutor.CallerRunsPolicy 中,線程調用運行該任務的 execute 本身。此策略提供簡單的反饋控制機制,能夠減緩新任務的提交速度。

3.在 ThreadPoolExecutor.DiscardPolicy 中,不能執行的任務將被刪除。

4.在 ThreadPoolExecutor.DiscardOldestPolicy 中,如果執行程序尚未關閉,則位於工作隊列頭部的任務將被刪除,然後重試執行程序(如果再次失敗,則重復此過程)。

定義和使用其他種類的 RejectedExecutionHandler 類也是可能的,但這樣做需要非常小心,尤其是當策略僅用於特定容量或排隊策略時。

public class Test {
    public static void main(String[] args) throws InterruptedException {
        //可以使用不同的abort和queue策略來看下執行的效果
        RejectedExecutionHandler handler = new ThreadPoolExecutor.DiscardOldestPolicy  ();
        ThreadPoolExecutor executor = new ThreadPoolExecutor(1, 2,
                200,
                TimeUnit.MILLISECONDS,
                new LinkedBlockingDeque<>(2),
                handler);
        for (int i = 0; i < 30; i++) {
            MyTask myTask = new MyTask(i);
            try {
                executor.execute(myTask);
            }catch (RejectedExecutionException e){
                System.out.println("task "+i+" 被放棄");
            }

            System.out.println("線程池中線程數目:" + executor.getPoolSize() + ",隊列中等待執行的任務數目:" +
                    executor.getQueue().size() + ",已執行完的任務數目:" + executor.getCompletedTaskCount());
        }
        //Thread.currentThread().sleep(400000);
        executor.shutdown();


    }
}

class MyTask implements Runnable {
    private int taskNum;

    public MyTask(int num) {
        this.taskNum = num;
    }

    @Override
    public void run() {
        System.out.println("正在執行task " + taskNum);

        try {
            Thread.currentThread().sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("task " + taskNum + "執行完畢");
    }
}

4、線程池的配置策略

通常情況下,這是一個復雜的活。

1.根據任務性質設置

要想合理的配置線程池,就必須首先分析任務特性,可以從以下幾個角度來進行分析:

1)任務的性質:CPU密集型任務,IO密集型任務和混合型任務。

2)任務的優先級:高,中和低。

3)任務的執行時間:長,中和短。

4)任務的依賴性:是否依賴其他系統資源,如數據庫連接。

任務性質不同的任務可以用不同規模的線程池分開處理。CPU密集型任務配置盡可能小的線程,如配置CPU數+1個線程的線程池。IO密集型任務則由於線程並不是一直在執行任務,則配置盡可能多的線程,如2*CPU數。混合型的任務,如果可以拆分,則將其拆分成一個CPU密集型任務和一個IO密集型任務,只要這兩個任務執行的時間相差不是太大,那麽分解後執行的吞吐率要高於串行執行的吞吐率,如果這兩個任務執行時間相差太大,則沒必要進行分解。我們可以通過Runtime.getRuntime().availableProcessors()方法獲得當前設備的CPU個數。

執行時間不同的任務可以交給不同規模的線程池來處理,或者也可以使用優先級隊列,讓執行時間短的任務先執行。

依賴數據庫連接池的任務,因為線程提交SQL後需要等待數據庫返回結果,如果等待的時間越長CPU空閑時間就越長,那麽線程數應該設置越大,這樣才能更好的利用CPU。

建議使用有界隊列,有界隊列能增加系統的穩定性和預警能力,可以根據需要設大一點,比如幾千。我在測試一個線程池的時候,使用循環不斷提交新的任務,造成任務積壓在線程池,最後程序不斷的拋出拋棄任務的異常。如果使用無界隊列,線程池的隊列就會越來越多,有可能會撐滿內存,導致整個系統不可用,而不只是後臺任務出現問題。

通常這種設置方式是比較粗略的方式。

2.利特爾法則

利特爾法則(Little’s law)是說,一個系統請求數等於請求的到達率與平均每個單獨請求花費的時間之乘積

我們可以使用利特爾法則(Little’s law)來判定線程池大小。我們只需計算請求到達率和請求處理的平均時間。然後,將上述值放到利特爾法則(Little’s law)就可以算出系統平均請求數。若請求數小於我們線程池的大小,就相應地減小線程池的大小。與之相反,如果請求數大於線程池大小,事情就有點復雜了。

當遇到有更多請求待處理的情況時,我們首先需要評估系統是否有足夠的能力支持更大的線程池。準確評估的前提是,我們必須評估哪些資源會限制應用程序的擴展能力。在本文中,我們將假定是CPU,而在實際中可能是其它資源。最簡單的情況是,我們有足夠的空間增加線程池的大小。若沒有的話,你不得不考慮其它選項,如軟件調優、增加硬件,或者調優並增加硬件。

具體的我們可以參考這篇文章:

http://www.infoq.com/cn/articles/Java-Thread-Pool-Performance-Tuning

3.配置文件中配置

如果是對系統性能非常重要的一個線程池,與其猜測該線程池的合理大小,不如將它的參數開放出來。因為線程池的合理大小和系統資源也是息息相關的,假設你在設備A上面的線程池大小已經是最優了,不見得把程序放到設備B上面同樣是最優的。放在配置文件中,可以方便將來根據系統運行情況進行調整。

我們看看開源任務調度框架Quartz開放了哪些參數:

<!-- 線程執行器配置,用於任務註冊 -->

<bean id="executor"class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor">

<property name="corePoolSize"value="6" />

<property name="maxPoolSize"value="16" />

<property name="queueCapacity"value="500" />

</bean>

Quart開放了核心線程數目、最大線程數目、任務隊列的容量這三個重要的參數,他們的設置都是和系統資源息息相關的。

4、線程池的監控

通過線程池提供的參數進行監控。線程池裏有一些屬性在監控線程池的時候可以使用:

taskCount:線程池需要執行的任務數量。

completedTaskCount:線程池在運行過程中已完成的任務數量。小於或等於taskCount。

largestPoolSize:線程池曾經創建過的最大線程數量。通過這個數據可以知道線程池是否滿過。如等於線程池的最大大小,則表示線程池曾經滿了。

getPoolSize:線程池的線程數量。如果線程池不銷毀的話,池裏的線程不會自動銷毀,所以這個大小只增不+ getActiveCount:獲取活動的線程數。

通過擴展線程池進行監控。通過繼承線程池並重寫線程池的beforeExecute,afterExecute和terminated方法,我們可以在任務執行前,執行後和線程池關閉前幹一些事情。如監控任務的平均執行時間,最大執行時間和最小執行時間等。

Java線程池的配置