Android 多線程:線程池理解和使用總結

Android線程池目錄.png

一、Android線程池介紹

1.1 原理

Android中的線程池概念來源于Java中的Executor,Executor是一個接口,真正的線程的實現(xiàn)為ThreadPoolExecutor。(ThreadPoolExecutor繼承了AbstractExecutorService,AbstractExecutorService是ExecutorService的實現(xiàn)類,ExecutorService繼承了Executor接口)。

1.2 分類

從線程池的功能特性上來說,Android中線程池主要分為4類:

  • FixedThreadPool(線程數(shù)量固定)、
  • CachedThreadPool(線程數(shù)量不定)、
  • ScheduledThreadPool(核心線程數(shù)量固定,非核心線程數(shù)量無限制)、
  • SingleThreadExecutor(只有一個核心線程,所有任務在里面按順序執(zhí)行)。

1.3 優(yōu)點

  1. 重用線程池中的線程,避免頻繁創(chuàng)建和銷毀線程所帶來的內存開銷。
  2. 有效控制線程的最大并發(fā)數(shù),避免因線程之間搶占資源而導致的阻塞現(xiàn)象。
  3. 能夠對線程進行簡單的管理,提供定時執(zhí)行以及指定時間間隔循環(huán)執(zhí)行等功能。

二、 Android線程池分類

由于Android中的線程池都是直接或間接通過配置ThreadPoolExecutor來實現(xiàn)的,因此在介紹它們之前要先介紹ThreadPoolExecutor。

2.1 ThreadPoolExecutor介紹

ThreadPoolExecutor有多個構造方法以及參數(shù),下面介紹比較常用的構造方法。

    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             threadFactory, defaultHandler);
    }
  • corePoolSize:線程池的核心線程數(shù),默認情況下核心線程會一直存活于線程池,即使處于閑置狀態(tài)。如果設置了ThreadPoolExecutor里的allowCoreThreadTimeOut為True,則核心線程在等待新任務到來時會有超時終止策略。超時的時間間隔由keepAliveTime所決定。
    private volatile long keepAliveTime;
    private volatile boolean allowCoreThreadTimeOut;
  • maximumPoolSize:線程池所能容納的最大線程數(shù),當線程數(shù)達到這個數(shù)值后,后續(xù)的新任務將會被阻塞。
  • keepAliveTime:非核心線程超時時長,超過這個時間沒有任務執(zhí)行,非核心線程就會被回收。當核心線程設置allowCoreThreadTimeOut為true時,keepAliveTime同樣作用于核心線程。
  • unit:用于指定keepAliveTime參數(shù)的時間單位,這是一個枚舉
    ,常用的有TimeUnit.MILLISECONDS(毫秒)、TimeUnit.SECOUNDS(秒)以及TimeUnit.MINUTS(分鐘)等,并且每個變量提供了轉換函數(shù)方便各個單位數(shù)據(jù)的轉換。
  • workQueue:線程池中的任務隊列,通過線程池的execute方法提交的Runnable對象會存儲在這個參數(shù)中。
  • threadFactory:線程工廠,為線程池提供創(chuàng)建新線程的功能。ThreadFactory是一個接口,它只有一個方法:Thread newThread(Runnable r);
  • handler:該handler的類型為RejectedExecutionHandler。這個參數(shù)不常用,它的作用是當線程池無法執(zhí)行新任務時,會調用handler的rejectedExecution(Runnable r, ThreadPoolExecutor e)方法來拋出異常。無法執(zhí)行的原因可能是由于隊列滿或者任務無法成功執(zhí)行等。
    ThreadPoolExecutor執(zhí)行任務大致遵循以下規(guī)則:
    (1)如果線程池中的線程數(shù)量未達到核心線程的數(shù)量,會直接啟動一個核心線程來執(zhí)行任務。
    (2)如果線程池中的線程數(shù)量已經(jīng)達到或者超過核心線程的數(shù)量,那么任務會被插入到任務隊列中排隊等待執(zhí)行。
    (3)如果第2步中無法插入新任務,說明任務隊列已滿,如果未達到規(guī)定的最大線程數(shù)量,則啟動一個非核心線程來執(zhí)行任務。
    (4)如果第3步中線程數(shù)量超過規(guī)定的最大值,則拒絕任務并使用RejectedExecutionHandler的rejectedExecution(Runnable r, ThreadPoolExecutor e)方法來通知調用者。

2.2 線程池的分類

先介紹一個類Executors,這個類定義了各種參數(shù)用于創(chuàng)建多種線程池,最終實現(xiàn)還是通過ThreadPoolExecutor類。
1. FixedThreadPool (Fixed:固定的,不變的)
通過Executors的newFixedThreadPool創(chuàng)建,通過創(chuàng)建時的參數(shù)可以看出又以下幾個特點:

  • 線程數(shù)量固定且都是核心線程:核心線程數(shù)量和最大線程數(shù)量都是nThreads;
  • 都是核心線程且不會被回收,快速相應外界請求;
  • 沒有超時機制,任務隊列也沒有大小限制;
  • 新任務使用核心線程處理,如果沒有空閑的核心線程,則排隊等待執(zhí)行。
    public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }

2. CachedThreadPool (Cached:緩存)
通過Executors的newCachedThreadPool創(chuàng)建,特點:

  • 線程數(shù)量不定,只有非核心線程,最大線程數(shù)任意大:傳入核心線程數(shù)量的參數(shù)為0,最大線程數(shù)為Integer.MAX_VALUE;
  • 有新任務時使用空閑線程執(zhí)行,沒有空閑線程則創(chuàng)建新的線程來處理。
  • 該線程池的每個空閑線程都有超時機制,時常為60s(參數(shù):60L, TimeUnit.SECONDS),空閑超過60s則回收空閑線程。
  • 適合執(zhí)行大量的耗時較少的任務,當所有線程閑置超過60s都會被停止,所以這時幾乎不占用系統(tǒng)資源。
    public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }

3. ScheduledThreadPool(Scheduled:預定的、排定的)
通過Executors的newScheduledThreadPool創(chuàng)建,特點:

  • 核心線程數(shù)量固定,非核心線程數(shù)量無限制;
  • 非核心線程閑置超過10s會被回收;
  • 主要用于執(zhí)行定時任務和具有固定周期的重復任務;
  • 四個里面唯一一個有延時執(zhí)行和周期重復執(zhí)行的功能:創(chuàng)建時
    ScheduledThreadPoolExecutor(corePoolSize)返回的是new ScheduledThreadPoolExecutor對象,ScheduledThreadPoolExecutor是ThreadPoolExecutor的子類,DelayedWorkQueue是ScheduledThreadPoolExecutor的一個靜態(tài)內部類,主要用于處理任務隊列延遲的工作。
    public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
        return new ScheduledThreadPoolExecutor(corePoolSize);
    }
    //默認閑置超時回收時常
    private static final long DEFAULT_KEEPALIVE_MILLIS = 10L;
    public ScheduledThreadPoolExecutor(int corePoolSize) {
        super(corePoolSize, Integer.MAX_VALUE,
              DEFAULT_KEEPALIVE_MILLIS, MILLISECONDS,
              new DelayedWorkQueue());
    }

4. SingleThreadExecutor (單線程線程池)
通過Executors的newSingleThreadExecutor創(chuàng)建,特點:

  • 只有一個核心線程,所有任務在同一個線程按順序執(zhí)行。
  • 所有的外界任務統(tǒng)一到一個線程中,所以不需要處理線程同步的問題。
    public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }

三、 Android線程池簡單使用

3.1 線程池簡單使用

(1)上面所說的四種常用線程池的實例化:

    //創(chuàng)建一個Runnable對象
    Runnable runnable = new Runnable() {
        @Override
        public void run() {
                // do something
        }
    };
    //四種線程池執(zhí)行Runnable對象
    ExecutorService fixedThreadPool = Executors.newFixedThreadPool(1);
    fixedThreadPool.execute(runnable);
    
    ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
    cachedThreadPool.execute(runnable);

    // 注意這里創(chuàng)建的是ScheduledExecutorService對象,ScheduledExecutorService是ExecutorService的子類
    ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(4);
    // 1000ms后執(zhí)行runnable
    scheduledThreadPool.schedule(runnable,1000,TimeUnit.MILLISECONDS);
    // 1000ms后,每3000ms執(zhí)行一次runnable
    scheduledThreadPool.scheduleAtFixedRate(runnable,1000,2000,TimeUnit.MILLISECONDS);
    isRunning = true;
    
    ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
    singleThreadExecutor.execute(runnable);

(2)簡單使用小demo:
添加線程池開始執(zhí)行和ScheduledThreadPool停止執(zhí)行的兩個按鈕點擊事件,布局就不貼了。

findViewById(R.id.btn_start).setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            count = 0;
            mStatueText.setText("線程開始執(zhí)行,次數(shù):"+ count);
            startThreadPool();
        }
    });
findViewById(R.id.btn_stop).setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            if(null != scheduledThreadPool && isRunning){
                scheduledThreadPool.shutdown();
                mStatueText.setText("scheduledThreadPool線程停止,當前次數(shù):"+ count);
                isRunning = false;
            }
        }
    });

startThreadPool();方法里Runnable每執(zhí)行一次增加一次count并打到TextView上:

    Runnable runnable = new Runnable() {
        @Override
        public void run() {
                count++;
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        mStatueText.setText("線程執(zhí)行完畢,次數(shù):"+ count);
                    }
                });
        }
    };

接著讓所有的線程池執(zhí)行這個Runnable對象,最后的結果是變量count的值從0直接到了3,然后又到5最后無限增長。
原因是除ScheduledThreadPool對象外的三個線程池很快執(zhí)行了任務,ScheduledThreadPool對象的執(zhí)行了兩次任務,兩個都延時1000ms。最后循環(huán)執(zhí)行的任務一直在增加count的值。

相關文章:
Android多線程:理解和簡單使用總結

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

友情鏈接更多精彩內容