1. 程式人生 > >對執行緒池的認識

對執行緒池的認識

說到執行緒,我想大家都不陌生,因為在開發時候或多或少都會用到執行緒,而通常建立執行緒有兩種方式:

1、繼承Thread類 
2、實現Runnable介面

雖說這兩種方式都可以創建出一個執行緒,不過它們之間還是有一點區別的,主要區別在於在多執行緒訪問同一資源的情況下,用Runnable介面建立的執行緒可以處理同一資源,而用Thread類建立的執行緒則各自獨立處理,各自擁有自己的資源。

所以,在Java中大多數多執行緒程式都是通過實現Runnable來完成的,而對於Android來說也不例外,當涉及到需要開啟執行緒去完成某件事時,我們都會這樣寫:

new Thread(new Runnable() {

@Override

public void run() {

//do sth .

}

}).start();

這段程式碼建立了一個執行緒並執行,它在任務結束後GC會自動回收該執行緒,一切看起來如此美妙,是的,它線上程併發不多的程式中確實不錯,而假如這個程式有很多地方需要開啟大量執行緒來處理任務,那麼如果還是用上述的方式去建立執行緒處理的話,那麼將導致系統的效能表現的非常糟糕,更別說在記憶體有限的移動裝置上,主要的影響如下:

1、執行緒的建立和銷燬都需要時間,當有大量的執行緒建立和銷燬時,那麼這些時間的消耗則比較明顯,將導致效能上的缺失

2、大量的執行緒建立、執行和銷燬是非常耗cpu和記憶體的,這樣將直接影響系統的吞吐量,導致效能急劇下降,如果記憶體資源佔用的比較多,還很可能造成OOM

3、大量的執行緒的建立和銷燬很容易導致GC頻繁的執行,從而發生記憶體抖動現象,而發生了記憶體抖動,對於移動端來說,最大的影響就是造成介面卡頓

而針對上述所描述的問題,解決的辦法歸根到底就是:重用已有的執行緒,從而減少執行緒的建立。

這就引出了執行緒池,執行緒池負責管理工作執行緒,包含一個等待執行的任務佇列。執行緒池的任務佇列是一個Runnable集合,工作執行緒負責從任務佇列中取出並執行Runnable物件。

使用執行緒池管理執行緒的優點

1、執行緒的建立和銷燬由執行緒池維護,一個執行緒在完成任務後並不會立即銷燬,而是由後續的任務複用這個執行緒,從而減少執行緒的建立和銷燬,節約系統的開銷

2、執行緒池旨線上程的複用,這就可以節約我們用以往的方式建立執行緒和銷燬所消耗的時間,減少執行緒頻繁排程的開銷,從而節約系統資源,提高系統吞吐量

3、在執行大量非同步任務時提高了效能

4、Java內建的一套ExecutorService執行緒池相關的api,可以更方便的控制執行緒的最大併發數、執行緒的定時任務、單執行緒的順序執行等                                                      

既然執行緒池就是ThreadPoolExecutor,所以我們要建立一個執行緒池只需要new ThreadPoolExecutor(…);就可以建立一個執行緒池,而如果這樣建立執行緒池的話,我們需要配置一堆東西,非常麻煩,我們可以看一下它的構造方法就知道了:

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

官方也不推薦使用這種方法來建立執行緒池,而是推薦使用Executors的工廠方法來建立執行緒池,Executors類是官方提供的一個工廠類,它裡面封裝好了眾多功能不一樣的執行緒池,從而使得我們建立執行緒池非常的簡便,主要提供瞭如下五種功能不一樣的執行緒池:

1、newFixedThreadPool() : 
作用:該方法返回一個固定執行緒數量的執行緒池,該執行緒池中的執行緒數量始終不變,即不會再建立新的執行緒,也不會銷燬已經建立好的執行緒,自始自終都是那幾個固定的執行緒在工作,所以該執行緒池可以控制執行緒的最大併發數。 
栗子:假如有一個新任務提交時,執行緒池中如果有空閒的執行緒則立即使用空閒執行緒來處理任務,如果沒有,則會把這個新任務存在一個任務佇列中,一旦有執行緒空閒了,則按FIFO方式處理任務佇列中的任務。

2、newCachedThreadPool() : 
作用:該方法返回一個可以根據實際情況調整執行緒池中執行緒的數量的執行緒池。即該執行緒池中的執行緒數量不確定,是根據實際情況動態調整的。 
栗子:假如該執行緒池中的所有執行緒都正在工作,而此時有新任務提交,那麼將會建立新的執行緒去處理該任務,而此時假如之前有一些執行緒完成了任務,現在又有新任務提交,那麼將不會建立新執行緒去處理,而是複用空閒的執行緒去處理新任務。那麼此時有人有疑問了,那這樣來說該執行緒池的執行緒豈不是會越集越多?其實並不會,因為執行緒池中的執行緒都有一個“保持活動時間”的引數,通過配置它,如果執行緒池中的空閒執行緒的空閒時間超過該“儲存活動時間”則立刻停止該執行緒,而該執行緒池預設的“保持活動時間”為60s。

3、newSingleThreadExecutor() : 
作用:該方法返回一個只有一個執行緒的執行緒池,即每次只能執行一個執行緒任務,多餘的任務會儲存到一個任務佇列中,等待這一個執行緒空閒,當這個執行緒空閒了再按FIFO方式順序執行任務佇列中的任務。

4、newScheduledThreadPool() : 
作用:該方法返回一個可以控制執行緒池內執行緒定時或週期性執行某任務的執行緒池。

5、newSingleThreadScheduledExecutor() : 
作用:該方法返回一個可以控制執行緒池內執行緒定時或週期性執行某任務的執行緒池。只不過和上面的區別是該執行緒池大小為1,而上面的可以指定執行緒池的大小。

好了,寫了一堆來介紹這五種執行緒池的作用,接下來就是獲取這五種執行緒池,通過Executors的工廠方法來獲取:

ExecutorService fixedThreadPool = Executors.newFixedThreadPool(5);
ExecutorService singleThreadPool = Executors.newSingleThreadExecutor();
ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);
ScheduledExecutorService singleThreadScheduledPool = Executors.newSingleThreadScheduledExecutor();

我們可以看到通過Executors的工廠方法來建立執行緒池極其簡便,其實它的內部還是通過new ThreadPoolExecutor(…)的方式建立執行緒池的,我們看一下這些工廠方法的內部實現:

        public static ExecutorService newFixedThreadPool(int nThreads) {
            return new ThreadPoolExecutor(nThreads, nThreads,
                                          0L, TimeUnit.MILLISECONDS,
                                          new LinkedBlockingQueue<Runnable>());
        }
        public static ExecutorService newSingleThreadExecutor() {
            return new FinalizableDelegatedExecutorService
                (new ThreadPoolExecutor(1, 1,
                                        0L, TimeUnit.MILLISECONDS,
                                        new LinkedBlockingQueue<Runnable>()));
        }
        public static ExecutorService newCachedThreadPool() {
            return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                          60L, TimeUnit.SECONDS,
                                          new SynchronousQueue<Runnable>());
        }

我們可以清楚的看到這些方法的內部實現都是通過建立一個ThreadPoolExecutor物件來建立的,正所謂萬變不離其宗,所以我們要了解執行緒池還是得了解ThreadPoolExecutor這個執行緒池類,其中由於和定時任務相關的執行緒池比較特殊(newScheduledThreadPool()、newSingleThreadScheduledExecutor()),它們建立的執行緒池內部實現是由ScheduledThreadPoolExecutor這個類實現的,而ScheduledThreadPoolExecutor是繼承於ThreadPoolExecutor擴充套件而成的,所以本質還是一樣的,只不過多封裝了一些定時任務相關的api,所以我們主要就是要了解ThreadPoolExecutor,從構造方法開始:

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

構造方法的引數有七個,下面一一來說明這些引數的作用:

corePoolSize:執行緒池中的核心執行緒數量 
maximumPoolSize:執行緒池中的最大執行緒數量 
keepAliveTime:這個就是上面說到的“保持活動時間“,上面只是大概說明了一下它的作用,不過它起作用必須在一個前提下,就是當執行緒池中的執行緒數量超過了corePoolSize時,它表示多餘的空閒執行緒的存活時間,即:多餘的空閒執行緒在超過keepAliveTime時間內沒有任務的話則被銷燬。而這個主要應用在快取執行緒池中 
unit:它是一個列舉型別,表示keepAliveTime的單位,常用的如:TimeUnit.SECONDS(秒)、TimeUnit.MILLISECONDS(毫秒) 
workQueue:任務佇列,主要用來儲存已經提交但未被執行的任務,不同的執行緒池採用的排隊策略不一樣,稍後再講 
threadFactory:執行緒工廠,用來建立執行緒池中的執行緒,通常用預設的即可 
handler:通常叫做拒絕策略,1、線上程池已經關閉的情況下 2、任務太多導致最大執行緒數和任務佇列已經飽和,無法再接收新的任務 。在上面兩種情況下,只要滿足其中一種時,在使用execute()來提交新的任務時將會拒絕,而預設的拒絕策略是拋一個RejectedExecutionException異常

 未完待續。。。。。。。