安卓線程池的原理及其應用

一:使用線程池的原因

在android開發中經常會使用多線程異步來處理相關任務,而如果用傳統的newThread來創建一個子線程進行處理,會造成一些嚴重的問題:

1:在任務衆多的情況下,系統要爲每一個任務創建一個線程,而任務執行完畢後會銷燬每一個線程,所以會造成線程頻繁地創建與銷燬。

2:多個線程頻繁地創建會佔用大量的資源,並且在資源競爭的時候就容易出現問題,同時這麼多的線程缺乏一個統一的管理,容易造成界面的卡頓。

3:多個線程頻繁地銷燬,會頻繁地調用GC機制,這會使性能降低,又非常耗時。

總而言之:頻繁地爲每一個任務創建一個線程,缺乏統一管理,降低性能,並且容易出現問題。

爲了解決這些問題,就要用到今天的主角——線程池.

線程池使用的好處:

1:對多個線程進行統一地管理,避免資源競爭中出現的問題。

2:(重點):對線程進行復用,線程在執行完任務後不會立刻銷燬,而會等待另外的任務,這樣就不會頻繁地創建、銷燬線程和調用GC。

3:JAVA提供了一套完整的ExecutorService線程池創建的api,可創建多種功能不一的線程池,使用起來很方便。

二:幾種常見的線程池

1:ThreadPoolExecutor 創建基本線程池

創建線程池,主要是利用ThreadPoolExecutor這個類,而這個類有幾種構造方法,其中參數最多的一種構造方法如下:

 

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

corePoolSize: 該線程池中核心線程的數量。

maximumPoolSize:該線程池中最大線程數量。(區別於corePoolSize)

keepAliveTime:從字面上就可以理解,是非核心線程空閒時要等待下一個任務到來的時間,當任務很多,每個任務執行時間很短的情況下調大該值有助於提高線程利用率。注意:當allowCoreThreadTimeOut屬性設爲true時,該屬性也可用於核心線程。

unit:上面時間屬性的單位

workQueue:任務隊列,後面詳述。

threadFactory:線程工廠,可用於設置線程名字等等,一般無須設置該參數。

設置好幾個參數就可以創建一個基本的線程池,而之後的各種線程池都是在這種基本線程池的基礎上延伸的。

下面貼個自己寫的demo來熟悉具體的使用並且加深影響:

 

//創建基本線程池
        final ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(3,5,1,TimeUnit.SECONDS,
                new LinkedBlockingQueue<Runnable>(100));

設置一個按鈕mThreadPoolExecute,並在點擊事件中使用線程池

 

 /**
  * 基本線程池使用
  */
 mThreadPoolExecute.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {

                for(int i = 0;i<30;i++){
                    final int finali = i;
                    Runnable runnable = new Runnable() {
                        @Override
                        public void run() {
                            try {
                                Thread.sleep(2000);
                                Log.d("Thread", "run: "+finali);
                                Log.d("當前線程:",Thread.currentThread().getName());
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                        }
                    };
                    threadPoolExecutor.execute(runnable);
                }
            }
        });

結果會每2s打印三個日誌。

具體過程:

1.execute一個線程之後,如果線程池中的線程數未達到核心線程數,則會立馬啓用一個核心線程去執行。

2.execute一個線程之後,如果線程池中的線程數已經達到核心線程數,且workQueue未滿,則將新線程放入workQueue中等待執行。

3.execute一個線程之後,如果線程池中的線程數已經達到核心線程數但未超過非核心線程數,且workQueue已滿,則開啓一個非核心線程來執行任務。

4.execute一個線程之後,如果線程池中的線程數已經超過非核心線程數,則拒絕執行該任務,採取飽和策略,並拋出RejectedExecutionException異常。

demo中設置的任務隊列長度爲100,所以不會開啓額外的5-3=2個非核心線程,如果將任務隊列設爲25,則前三個任務被核心線程執行,剩下的30-3=27個任務進入隊列會滿,此時會開啓2個非核心線程來執行剩下的兩個任務。

 

//新開啓了thread-4與thread-5執行剩下的超出隊列的兩個任務28和29
2019-03-28 15:54:07.879 22284-22618/com.example.threadpooltest D/Thread:: 1
2019-03-28 15:54:07.879 22284-22617/com.example.threadpooltest D/Thread:: 0
2019-03-28 15:54:07.879 22284-22617/com.example.threadpooltest D/當前線程:: pool-1-thread-1
2019-03-28 15:54:07.879 22284-22618/com.example.threadpooltest D/當前線程:: pool-1-thread-2
2019-03-28 15:54:07.880 22284-22619/com.example.threadpooltest D/Thread:: 2
2019-03-28 15:54:07.880 22284-22619/com.example.threadpooltest D/當前線程:: pool-1-thread-3
2019-03-28 15:54:07.881 22284-22620/com.example.threadpooltest D/Thread:: 28
2019-03-28 15:54:07.881 22284-22620/com.example.threadpooltest D/當前線程:: pool-1-thread-4
2019-03-28 15:54:07.881 22284-22621/com.example.threadpooltest D/Thread:: 29
2019-03-28 15:54:07.881 22284-22621/com.example.threadpooltest D/當前線程:: pool-1-thread-5

疑問:每個for循環裏都有一個sleep(2000),爲何會每隔2s打印三個任務?
原因:因爲一開始的時候只是聲明runnable對象並且重寫run()方法,並沒有運行,而後execute(runnable) 纔會sleep,又因爲一開始創建線程池的時候聲明的核心線程數爲3,所以會首先開啓三個核心線程,然後執行各自的run方法,雖然有先後順序,但這之間的間隔很短,所以2s後同時打印3個任務。

2:FixedThreadPool (可重用固定線程數)

Executors類中的創建方法:

 

FixedThreadPool創建

特點:參數爲核心線程數,只有核心線程,無非核心線程,並且阻塞隊列無界。

demo代碼:

創建:

 

  //創建fixed線程池

  final ExecutorService fixedThreadPool = Executors.newFixedThreadPool(5);

使用:

 

    /**
     * fixed線程池
     */
        mFixedPoolThread.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                for(int i = 0;i<30;i++){
                    final int finali = i;
                    Runnable runnable = new Runnable() {
                        @Override
                        public void run() {
                            try {
                                Thread.sleep(2000);
                                Log.d("Thread", "run: "+finali);
                                Log.d("當前線程:",Thread.currentThread().getName());
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                        }
                    };
                    fixedThreadPool.execute(runnable);

                }
            }
        });

結果爲每2s打印5次任務,跟上面的基礎線程池類似。

3:CachedThreadPool (按需創建)

Executors類中的創建方法:

 

CachedThreadPool創建

特點:沒有核心線程,只有非核心線程,並且每個非核心線程空閒等待的時間爲60s,採用SynchronousQueue隊列。

demo代碼:

創建:

 

 //創建Cached線程池

 final ExecutorService cachedThreadPool = Executors.newCachedThreadPool();

使用:

 

  /**
    * cached線程池
    */
        mCachedPoolThread.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                for(int i = 0;i<30;i++){
                    final int finali = i;
                    Runnable runnable = new Runnable() {
                        @Override
                        public void run() {
                            try {
                                Thread.sleep(2000);
                                Log.d("Thread", "run: "+finali);
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                        }
                    };
                    cachedThreadPool.execute(runnable);

                }
            }
        });

結果:過2s後直接打印30個任務

結果分析:

  • 因爲沒有核心線程,其他全爲非核心線程,SynchronousQueue是不存儲元素的,每次插入操作必須伴隨一個移除操作,一個移除操作也要伴隨一個插入操作。
  • 當一個任務執行時,先用SynchronousQueue的offer提交任務,如果線程池中有線程空閒,則調用SynchronousQueue的poll方法來移除任務並交給線程處理;如果沒有線程空閒,則開啓一個新的非核心線程來處理任務。
  • 由於maximumPoolSize是無界的,所以如果線程處理任務速度小於提交任務的速度,則會不斷地創建新的線程,這時需要注意不要過度創建,應採取措施調整雙方速度,不然線程創建太多會影響性能。
  • 從其特點可以看出,CachedThreadPool適用於有大量需要立即執行的耗時少的任務的情況。

4:SingleThreadPool(單個核線的fixed)

創建方法:

 

SingleThreadPool創建

 

創建:

 

 //創建Single線程池
 final ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();

使用:

 

  /**
    * single線程池
    */
        mSinglePoolExecute.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                for(int i = 0;i<30;i++){
                    final int finali = i;x
                    Runnable runnable = new Runnable() {
                        @Override
                        public void run() {
                            try {
                                Thread.sleep(2000);
                                Log.d("Thread", "run: "+finali);
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                        }
                    };
                    singleThreadExecutor.execute(runnable);

                }
            }
        });

結果:每2s打印一個任務,由於只有一個核心線程,當被佔用時,其他的任務需要進入隊列等待。

5:ScheduledThreadPool(定時延時執行)

創建方法:

 

ScheduledThreadPool創建1

 

ScheduledThreadPool創建2

創建:

 

//創建Scheduled線程池
  final ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(3);

使用:

 

  /**
    * scheduled線程池
    */
        mScheduledTheadPool.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {

                    Runnable runnable = new Runnable() {
                        @Override
                        public void run() {

                            Log.d("Thread", "This task is delayed to execute");
                    }

                    };
                    scheduledThreadPool.schedule(runnable,10,TimeUnit.SECONDS);//延遲啓動任務

//延遲5s後啓動,每1s執行一次             scheduledThreadPool.scheduleAtFixedRate(runnable,5,1,TimeUnit.SECONDS);
//啓動後第一次延遲5s執行,後面延遲1s執行  scheduledThreadPool.scheduleWithFixedDelay(runnable,5,1,TimeUnit.SECONDS);
            }
        });

結果如代碼所述。

6:自定義的PriorityThreadPool(隊列中有優先級比較的線程池)

創建:

 

 //創建自定義線程池(優先級線程)
final ExecutorService priorityThreadPool = new ThreadPoolExecutor(3,3,0, TimeUnit.SECONDS,new PriorityBlockingQueue<Runnable>());

自定義Runnable,繼承Comparable接口:

 

public abstract class PriorityRunnable implements Runnable,Comparable<PriorityRunnable> {
    private int priority;

    public  PriorityRunnable(int priority){
        if(priority <0) {
            throw new IllegalArgumentException();
        }
        this.priority = priority;
    }

    public int getPriority() {
        return priority;
    }

    @Override
    public int compareTo(@NonNull PriorityRunnable another) {
        int me = this.priority;
        int anotherPri=another.getPriority();
        return me == anotherPri ? 0 : me < anotherPri ? 1 : -1;
    }

    @Override
    public void run() {
            doSomeThing();
    }

    protected abstract void doSomeThing();
}

利用抽象類繼承Comparable接口重寫其中的compareTo方法來比較優先級。

使用:

 

  mMyPriorityTheadPool.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                for(int i = 0;i<30;i++){
                    final int priority = i;
                    priorityThreadPool.execute(new PriorityRunnable(priority) {
                        @Override
                        protected void doSomeThing() {
                            Log.d("MainActivity", "優先級爲 "+priority+"  的任務被執行");
                            try {
                                Thread.sleep(2000);
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                        }
                    });

                }
            }
        });

結果:前三個任務被創建的三個核心線程執行,之後的27個任務進入隊列並且調用compareTo方法進行排序,之後打印出來的是經過排序後從大到小的順序。

三:JAVA中的阻塞隊列

由於上面的構造方法涉及到了阻塞隊列,所以補充一些阻塞隊列的知識。
阻塞隊列:我的理解是,生產者——消費者,生產者往隊列裏放元素,消費者取,如果隊列裏沒有元素,消費者線程取則阻塞,如果隊列裏元素滿了,則生產者線程阻塞。

常見的阻塞隊列有下列7種:

 

ArrayBlockingQueue :一個由數組結構組成的有界阻塞隊列。
LinkedBlockingQueue :一個由鏈表結構組成的有界阻塞隊列。
PriorityBlockingQueue :一個支持優先級排序的無界阻塞隊列。
DelayQueue:一個使用優先級隊列實現的無界阻塞隊列。
SynchronousQueue:一個不存儲元素的阻塞隊列。
LinkedTransferQueue:一個由鏈表結構組成的無界阻塞隊列。
LinkedBlockingDeque:一個由鏈表結構組成的雙向阻塞隊列。

具體情況具體分析,選擇合適的隊列。
由於只是補充部分,所以具體使用及實現原理請百度。

四:各個線程池總結及適用場景

newCachedThreadPool:

底層:返回ThreadPoolExecutor實例,corePoolSize爲0;maximumPoolSize爲Integer.MAX_VALUE;keepAliveTime爲60L;unit爲TimeUnit.SECONDS;workQueue爲SynchronousQueue(同步隊列)
通俗:當有新任務到來,則插入到SynchronousQueue中,由於SynchronousQueue是同步隊列,因此會在池中尋找可用線程來執行,若有可以線程則執行,若沒有可用線程則創建一個線程來執行該任務;若池中線程空閒時間超過指定大小,則該線程會被銷燬。
適用:執行很多短期異步的小程序或者負載較輕的服務器

newFixedThreadPool:

底層:返回ThreadPoolExecutor實例,接收參數爲所設定線程數量nThread,corePoolSize爲nThread,maximumPoolSize爲nThread;keepAliveTime爲0L(不限時);unit爲:TimeUnit.MILLISECONDS;WorkQueue爲:new LinkedBlockingQueue<Runnable>() 無解阻塞隊列
通俗:創建可容納固定數量線程的池子,每隔線程的存活時間是無限的,當池子滿了就不再添加線程了;如果池中的所有線程均在繁忙狀態,對於新任務會進入阻塞隊列中(無界的阻塞隊列)
適用:執行長期的任務,性能好很多

newSingleThreadExecutor:

底層:FinalizableDelegatedExecutorService包裝的ThreadPoolExecutor實例,corePoolSize爲1;maximumPoolSize爲1;keepAliveTime爲0L;unit爲:TimeUnit.MILLISECONDS;workQueue爲:new LinkedBlockingQueue<Runnable>() 無解阻塞隊列
通俗:創建只有一個線程的線程池,且線程的存活時間是無限的;當該線程正繁忙時,對於新任務會進入阻塞隊列中(無界的阻塞隊列)
適用:一個任務一個任務執行的場景

NewScheduledThreadPool:

底層:創建ScheduledThreadPoolExecutor實例,corePoolSize爲傳遞來的參數,maximumPoolSize爲Integer.MAX_VALUE;keepAliveTime爲0;unit爲:TimeUnit.NANOSECONDS;workQueue爲:new DelayedWorkQueue() 一個按超時時間升序排序的隊列
通俗:創建一個固定大小的線程池,線程池內線程存活時間無限制,線程池可以支持定時及週期性任務執行,如果所有線程均處於繁忙狀態,對於新任務會進入DelayedWorkQueue隊列中,這是一種按照超時時間排序的隊列結構
適用:週期性執行任務的場景

五:線程池其它方法:

 

1.shutDown()  關閉線程池,不影響已經提交的任務

2.shutDownNow() 關閉線程池,並嘗試去終止正在執行的線程

3.allowCoreThreadTimeOut(boolean value) 允許核心線程閒置超時時被回收

4.submit 一般情況下我們使用execute來提交任務,但是有時候可能也會用到submit,使用submit的好處是submit有返回值。

5.beforeExecute() - 任務執行前執行的方法

6.afterExecute() -任務執行結束後執行的方法

7.terminated() -線程池關閉後執行的方法

參考出處鏈接:

Android性能優化之使用線程池處理異步任務
Android開發之線程池使用總結
線程池的種類,區別和使用場景
Android 進階之光 ——劉望舒 (一本好書,推薦)



作者:無問o
鏈接:https://www.jianshu.com/p/7b2da1d94b42
來源:簡書
著作權歸作者所有。商業轉載請聯繫作者獲得授權,非商業轉載請註明出處。

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