Android 多線程和線程池

AsyncTask

使用 AsyncTask

須要繼承 AsyncTask,重寫 doInBackground 方法。
onPreExecute 運行在調度線程
doInBackground 運行在線程池中
onPostExecute / onProgressUpdate / onCancelled 運行在 UI 線程中。面試

原理

AsyncTask 內部有兩個靜態線程池
一個線程池 SERIAL_EXECUTOR 表示串行線程池,內部包含一個消息隊列,用來保存任務、按順序調度任務
一個 THREAD_POOL_EXECUTOR 用來執行任務,其線程數量是根據 CPU 核數計算的
一個跑在主線程的 Handler,把執行進度和執行結果的回調發送到主線程
這兩個線程池和一個 Handler 都是靜態的,其實是全部的 AsyncTask 對象公用的bash

特色和缺陷

  • 4.1以前版本須要在主線程完成初始化,如今的版本不須要初始化了
  • 5.0以及以前 AsyncTask 對象必須在主線程建立,execute 方法必須在主線程調用,以後的版本無此限制,可是 onPreExecute 方法會在調度線程執行
  • 一個 AsyncTask 只能執行一次,不然會報錯
  • 1.6以前是串行任務,1.6開始採用線程池處理任務,3.0開始又串行任務,但可使用 executeOnExecutor 並行
  • 通常會把 AsyncTask 寫成 Activity 的內部類,注意內存泄漏問題
  • 新版本中並行線程池的核心線程設置了超時策略,閒置30秒會自動銷燬,但舊版本核心線程不會被銷燬

參考

Android源碼分析—帶你認識不同的AsyncTask - 任玉剛
別再傻傻得認爲AsyncTask只能夠在主線程中建立實例和調用execute方法 - smileiam的專欄併發

HandlerThread

不使用 HandlerThread 建立異步 Handler的方法

class LooperThread extends Thread {
    public Handler mHandler;

    public void run() {
        Looper.prepare();
        mHandler = new Handler(){
            public void handlerMessage(Message msg){
                ...
            }
        };
        Looper.loop();
    }
複製代碼

HandlerThread 作了什麼

@Override
    public void run() {
        mTid = Process.myTid();
         // 爲當前線程建立 Looper
        Looper.prepare();
        synchronized (this) {
            // 若是建立完成以前調用了 getLooper 方法,會被阻塞
            mLooper = Looper.myLooper();
            // mLooper 已經完成初始化,若是有線程調用 getLooper 方法且被阻塞,喚醒它
            notifyAll();
        }
        Process.setThreadPriority(mPriority);
        // 若是重寫了 onLooperPrepared,會得到回調
        onLooperPrepared();
        // 進入循環
        Looper.loop();
        mTid = -1;
    }
複製代碼

HandlerThread 在 run 方法中建立 Looper 並開始循環,若是 Looper 建立完成以前調用 getLooper 會被阻塞,直到初始化完成。
另外 HandlerThread 提供了 quit 和 quitSafely 兩個方法,用於中止其 Looper。異步

IntentService

使用方法

繼承 IntentService,實現其 onHandleIntent 方法。ide

原理

IntentService 在 onCreate 方法中啓動了 HandlerThread 並建立了異步 Handler,而後將 onStartCommend 方法轉換到異步線程中執行,並回調給 onHandleIntent 方法。
IntentService 因爲在異步線程執行,因此是串行的,而且執行完全部任務後會自動中止 Service。oop

線程池

線程池的優勢

  1. 重用線程,自動建立、銷燬線程,減小開銷
  2. 能有效控制線程池的最大併發數,避免大量線程搶佔系統資源致使阻塞
  3. 可以對線程進行簡單的管理,並提供定時執行以及定時循環執行等功能

ThreadPoolExecutor

狀態和生命週期

  • RUNNING 運行中,能夠接受新任務並處理
  • SHUTDOWN 關閉狀態,不會接受新任務,可是會處理隊列中還存在的任務
  • STOP 中止狀態,不會接受新任務,也不處理隊列中的任務,直接中斷
  • TIDYING 中止狀態,表示全部任務已經終止了
  • TERMINATED 表示 terminated 方法執行完了

構造方法

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler)
複製代碼

參數說明:源碼分析

  • corePoolSize: 核心線程數量,默認會一直存活。若是 allowCoreThreadTimeOut 設置爲 true,核心線程也會超時停止。
  • maximumPoolSize: 最大線程數量,活動線程達到這個數值後,後續的新任務將會被阻塞。
  • keepAliveTime 和 unit: 非核心線程閒置時的超時時間,超過這個時長非核心線程就會被回收。當 allowCoreThreadTimeOut 屬性設置爲 true 時,keepAliveTime 一樣會做用於核心線程。
  • workQueue: 線程池中的任務隊列,經過線程池的 execute 方法提交的 Runnable 對象會存儲在這個參數中。
  • threadFactory: 線程工廠,能夠在建立線程時指定線程名稱。
  • handler: 沒法執行任務時的拒絕策略,有如下幾種
    • AbortPolicy 默認選項,直接拋出異常;
    • CallerRunsPolicy 在調用線程中執行;
    • DiscardPolicy 直接丟棄新任務;
    • DiscardOldestPolicy 丟棄最舊的任務;

工做流程

當有新的任務進入線程池中時:post

  1. 若是線程數量未達到核心線程的數量,會直接啓動一個核心線程;
  2. 若是線程數量已經達到或超過核心線程數量,那麼任務會被插入到任務隊列中等待執行;
  3. 若是沒法插入到任務隊列中(每每因爲任務隊列已滿),若是線程數量爲達到最大值,會啓動非核心線程;
  4. 若是任務隊列已滿,並且線程數量已經達到最大數量,就會按照拒絕策略執行。

線程池的分類

Executors 提供了幾種經常使用的線程池:ui

  1. FixedThreadPool 線程數量固定,核心線程數量和最大線程數量相等,沒有非核心線程也沒有超時機制,隊列無限大;
  2. CachedThreadPool 沒有核心線程,線程數量無限大,超時時間一分鐘,使用 SynchronousQueue 隊列存儲任務,實際上不會存儲任務;
  3. ScheduledThreadPool 核心線程固定,非核心線程無限大,超時時間是0,立刻被回收,用於定時任務或週期循環任務;
  4. SingleThreadExecutor 只有一個核心線程,沒有超時,隊列無限大。

注意事項

  1. 阿里 Java 開發手冊中禁止手動建立線程:使用線程池的好處是減小在建立和銷燬線程上所花的時間以及系統資源的開銷,解決資源不足的問題。若是不使用線程池,有可能形成系統建立大量同類線程而致使消耗過多資源或者「過分切換」的問題。
  2. 阿里 Java 開發手冊一樣禁止使用 Executors 去建立線程,而是經過 ThreadPoolExecutor 的方式,這樣的處理方式讓寫的同窗更加明確線程池的運行規則,避免資源耗盡的風險。
    緣由在於 FixedThreadPool 和 SingleThreadPool 容許的請求隊列長度爲 Integer.MAX_VALUE,可能會堆積大量請求,致使 OOM;CachedThreadPool 和 ScheduledThreadPool 容許建立的線程數量爲 Integer.MAX_VALUE,可能會建立大量的線程,致使 OOM。

參考資料

重走JAVA之路(五):面試又被問線程池原理?教你如何反擊 - 掘金this

相關文章
相關標籤/搜索