Java: 線程池(ThreadPoolExecutor)中的參數說明


最近在看《阿里巴巴Android開發手冊》,里面有這樣幾句話:

【強制】新建線程時,必須通過線程池提供(AsyncTask 或者ThreadPoolExecutor或者其他形式自定義的線程池),不允許在應用中自行顯式創建線程。
【強制】線程池不允許使用Executors 去創建,而是通過ThreadPoolExecutor 的方式,這樣的處理方式讓寫的同學更加明確線程池的運行規則,規避資源耗盡的風險。

個人對線程池的使用也有一段日子了,而且很多時候為了省事用的都是Executors的方式去創建,也沒什么問題,不過既然阿里的工程師這么說,自然有這么說的道理,以后還是盡量改用ThreadPoolExecutor的方式來創建吧,不過使用ThreadPoolExecutor方式來創建時,需要傳入的參數很多,為了加深記憶(老年人了腦子不好),特記錄之:

為什么要引入線程池

平時在Android開發的過程中經常會用到多線程異步處理相關任務,每開一個線程都要新建一個Thread對象來處理,這種操作會造成哪些后果呢?

1、系統執行多任務時,會為每個任務創建對應的線程,當任務執行結束之后會銷毀對應的線程,在這種情況下對象被頻繁的創建和銷毀。
2、當對線程象被頻繁時會占用大量的系統資源,在並發的過程中會造成資源競爭出現問題。大量的創建線程還會造成混亂,沒有一個統一的管理機制,容易造成應用卡頓。
3、大量線程對象被頻繁銷毀,將會頻繁出發GC機制,從而降低性能。

引入線程池的好處:

1、重用線程池中的線程,避免因頻繁創建和銷毀線程造成的性能消耗。
2、更加有效的控制線程的最大並發數,防止線程過多搶占資源造成的系統阻塞。
3、對線程進行有效的管理。

ThreadPoolExecutor的參數說明

這個類的構造函數如下:

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {
        if (corePoolSize < 0 ||
            maximumPoolSize <= 0 ||
            maximumPoolSize < corePoolSize ||
            keepAliveTime < 0)
            throw new IllegalArgumentException();
        if (workQueue == null || threadFactory == null || handler == null)
            throw new NullPointerException();
        this.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
    }

corePoolSize

核心線程數。在創建線程池之后,默認情況下線程池中並沒有任何的線程,而是等待任務到來才創建線程去執行任務,當線程池中的線程數目達到 corePoolSize后,新來的任務將會被添加到緩存隊列中,也就是那個workQueue,除非調用 ThreadPoolExecutor#prestartAllCoreThreads() 方法或者是 ThreadPoolExecutor # prestartCoreThread() 方法(從這兩個方法的名字就可以看出是預創建線程的意思,即在沒有任務到來之前就創建corePoolSize個線程或一個線程)。

PS:很多人不知道這個數該填多少合適,其實也不必特別糾結,根據實際情況填寫就好,實在不知道,就按照阿里工程師的寫法取下列值就好了:
int NUMBER_OF_CORES = Runtime.getRuntime().availableProcessors();

maximumPoolSize

線程池中的最大線程數。表示線程池中最多可以創建多少個線程,很多人以為它的作用是這樣的:”當線程池中的任務數超過 corePoolSize 后,線程池會繼續創建線程,直到線程池中的線程數小於maximumPoolSize“,其實這種理解是完全錯誤的。它真正的作用是:當線程池中的線程數等於 corePoolSize 並且 workQueue 已滿,這時就要看當前線程數是否大於 maximumPoolSize,如果小於maximumPoolSize 定義的值,則會繼續創建線程去執行任務, 否則將會調用去相應的任務拒絕策略來拒絕這個任務。另外超過 corePoolSize的線程被稱做"Idle Thread", 這部分線程會有一個最大空閑存活時間(keepAliveTime),如果超過這個空閑存活時間還沒有任務被分配,則會將這部分線程進行回收。

keepAliveTime

控制"idle Thread"的空閑存活時間。這個idle Thread就是上面提到的超過 corePoolSize 后新創建的那些線程,默認情況下,只有當線程池中的線程數大於corePoolSize,且這些"idle Thread"並沒有被分配任務時,這個參數才會起作用。另外,如果調用了 ThreadPoolExecutor#allowCoreThreadTimeOut(boolean) 的方法,在線程池中的線程數不大於corePoolSize,且這些core Thread 也沒有被分配任務時,keepAliveTime 參數也會起作用。

unit

參數keepAliveTime的時間單位,共7種取值,在TimeUtil中定義:

TimeUnit.DAYS;              //
TimeUnit.HOURS;             //小時
TimeUnit.MINUTES;           //分鍾
TimeUnit.SECONDS;           //
TimeUnit.MILLISECONDS;      //毫秒
TimeUnit.MICROSECONDS;      //微妙
TimeUnit.NANOSECONDS;       //納秒

workQueue

阻塞隊列。如果當前線程池中的線程數目>=corePoolSize,則每來一個任務,會嘗試將其添加到該隊列當中,注意只要超過了 corePoolSize 就會把任務添加到該緩存隊列,添加可能成功也可能不成功,如果成功的話就會等待空閑線程去執行該任務,若添加失敗(一般是隊列已滿),就會根據當前線程池的狀態決定如何處理該任務(若線程數 < maximumPoolSize 則新建線程;若線程數 >= maximumPoolSize,則會根據拒絕策略做具體處理)。

常用的阻塞隊列有:

1)ArrayBlockingQueue       //基於數組的先進先出隊列,此隊列創建時必須指定大小2)LinkedBlockingQueue      //基於鏈表的先進先出隊列,如果創建時沒有指定此隊列大小,則默認為Integer.MAX_VALUE; 3)synchronousQueue        //這個隊列比較特殊,它不會保存提交的任務,而是將直接新建一個線程來執行新來的任務。

threadFactory

線程工廠。用來為線程池創建線程,當我們不指定線程工廠時,線程池內部會調用Executors.defaultThreadFactory()創建默認的線程工廠,其后續創建的線程優先級都是Thread.NORM_PRIORITY。如果我們指定線程工廠,我們可以對產生的線程進行一定的操作。

handler

拒絕執行策略。當線程池的緩存隊列已滿並且線程池中的線程數目達到maximumPoolSize,如果還有任務到來就會采取任務拒絕策略,通常有以下四種策略:

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

參考鏈接:
1. 初探Android 線程池
2. Java並發編程:線程池的使用

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM