要點提煉|開發藝術之執行緒

釐米姑娘發表於2018-01-03

本篇的主要內容是Android的執行緒和執行緒池:

  • 概述
  • 執行緒形態
    • AsyncTask
    • HandlerThread
    • IntentService
  • 執行緒池

一.概述

1.含義:執行緒是作業系統排程的最小單元。

2.特點:執行緒是一種受限的系統資源。即執行緒不可無限制的產生且執行緒的建立和銷燬都有一定的開銷。

Q:如何避免頻繁建立和銷燬執行緒所帶來的系統開銷? A:採用執行緒池,池中會快取一定數量的執行緒,進而達到效果。

3.分類:

  • 按用途可分為兩類:
    • 主執行緒:一般一個程式只有一個執行緒,主要處理介面互動相關的邏輯。
    • 子執行緒:除主執行緒之外都是子執行緒,主要用於執行耗時操作
  • 按形態可分為三類:
    • AsyncTask:底層封裝了執行緒池和Handler,便於執行後臺任務以及在子執行緒中進行UI操作。
    • HandlerThread:一種具有訊息迴圈的執行緒,其內部可使用Handler。
    • IntentService:是一種非同步、會自動停止的服務,內部採用HandlerThread。

image


二.執行緒形態

對於主執行緒和子執行緒相信已經非常熟悉了,現在主要學習以下三種形態的執行緒:

1.AsyncTask

a.AsyncTask:一種輕量級的非同步任務類。

在Android中實現非同步任務機制有兩種方式:Handler和AsyncTask。

  • Handler機制存在的問題:程式碼相對臃腫;多工同時執行時不易精確控制執行緒。
  • 引入AsyncTask的好處:建立非同步任務更簡單,直接繼承它可方便實現後臺非同步任務的執行和進度的回撥更新UI,而無需編寫任務執行緒和Handler例項就能完成相同的任務。

b.AsyncTask是抽象的泛型類,其組成成員有:

  • 三個泛型引數:
    • Params:表示執行AsyncTask需要傳入的引數,可用於在後臺任務中使用;
    • Progress:表示後臺任務執行的進度;
    • Result: 表示後臺任務的返回結果的型別;
    • 若沒有傳遞具體的引數,這三個泛型引數都可使用void。如:
//含義:在執行AsyncTask時不需要傳入引數給後臺任務、使用整型資料來作為進度顯示單位,最後使用布林型資料來反饋執行結果
public abstract class AsyncTask<Void, Integer, Boolean>
複製程式碼
  • 五個核心方法:
    • onPreExecute()
      • 執行在:主執行緒
      • 呼叫時刻:在非同步任務執行之前被呼叫
      • 作用:可用於進行一些介面上的初始化操作
    • doInBackground(Params…params)
      • 執行在:子執行緒
      • 作用:可用於處理所有的耗時任務。若需要更新UI需呼叫 publishProgress(Progress...)方法
      • 注意:任務一旦完成就通過return語句將任務的執行結果返回,若Result被指定為void,就可不返回執行結果
    • onProgressUpdate(Progress…values)
      • 執行在:主執行緒
      • 呼叫時刻:在後臺任務中呼叫publishProgress(Progress...)之後該方法會被呼叫
      • 作用:可利用方法中攜帶的引數如Progress來對UI進行相應地更新
    • onPostExecute(Result result)
      • 執行在:主執行緒
      • 呼叫時刻:在非同步任務執行完畢並通過return語句返回時被呼叫
      • 作用:可利用方法中返回的資料來進行一些UI操作
    • onCancelled()
      • 執行在:主執行緒
      • 呼叫時刻:當非同步任務被取消時被呼叫
      • 作用:可用於做介面取消的更新
    • 注意:
      • 不要直接呼叫onPreExecute()、doInBackground()、onProgressUpdate()、onPostExecute)和onCancelled()方法
      • AsyncTask物件必須在主執行緒建立
  • 開始和結束非同步任務的方法:
    • execute(Params...params)
      • 必須在主執行緒中呼叫
      • 作用:表示開始一個非同步任務
      • 注意:一個非同步物件只能呼叫一次execute()方法
    • cancel(booleanmayInterruptIfRunning)
      • 必須在主執行緒中呼叫
      • 作用:表示停止一個非同步任務

比如自定義一個AsyncTask,來模擬一個下載任務:

class DownloadTask extends AsyncTask<Void, Integer, Boolean> {  
  
    @Override//初始化一個ProgressDialog  
    protected void onPreExecute() {  
        progressDialog.show();  
    }  
  
    @Override//具體的下載邏輯
    protected Boolean doInBackground(Void... params) {  
        try {  
            while (true) {  
                int downloadPercent = doDownload();  
                publishProgress(downloadPercent);  
                if (downloadPercent >= 100) {  
                    break;  
                }  
            }  
        } catch (Exception e) {  
            return false;  
        }  
        return true;  
    }  
  
    @Override//顯示當前的下載進度
    protected void onProgressUpdate(Integer... values) {  
        progressDialog.setMessage("當前下載進度:" + values[0] + "%");  
    }  
  
    @Override//提示任務的執行結果  
    protected void onPostExecute(Boolean result) {  
        progressDialog.dismiss();  
        if (result) {  
            Toast.makeText(context, "下載成功", Toast.LENGTH_SHORT).show();  
        } else {  
            Toast.makeText(context, "下載失敗", Toast.LENGTH_SHORT).show();  
        }  
    }  
}  
複製程式碼

任務的啟動和停止只需要以下幾行程式碼:

// 開始任務  
DownloadTask mDownloadTask  = new DownloadTask();  
mDownloadTask .execute();  
   
// 停止任務  
mDownloadTask .cancel(true);  
複製程式碼

補充例項詳解Android中AsyncTask的使用

c.工作原理

  • 內部有一個靜態的Handler物件即InternalHandler
    • 作用:將執行環境從執行緒池切換到主執行緒;通過它來傳送任務執行的進度以及執行結束等訊息。
    • 注意:必須在主執行緒中建立
  • 內部有兩個執行緒池:
    • SerialExecutor:用於任務的排隊,預設是序列的執行緒池
    • THREAD_POOL_EXECUTOR:用於真正執行任務。
  • 排隊執行過程:
    • 把引數Params封裝為FutureTask物件,相當於Runnable;
    • 呼叫SerialExecutor.execute()將FutureTask插入到任務佇列tasks;
    • 若沒有正在活動的AsyncTask任務,則就會執行下一個AsyncTask任務。執行完畢後會繼續執行其他任務直到所有任務都完成。即預設使用序列方式執行任務。

注意:AsyncTask不適用於進行特別耗時的後臺任務,而是建議用執行緒池。

推薦閱讀Android AsyncTask完全解析,帶你從原始碼的角度徹底理解AsyncTask原理及不足


2.HandlerThread

a.HandlerThread是一個執行緒類,它繼承自Thread

與普通Thread的區別:具有訊息迴圈的效果。原理:

  • 內部HandlerThread.run()方法中有Looper,通過Looper.prepare()來建立訊息佇列,並通過Looper.loop()來開啟訊息迴圈。

b實現方法

  • 例項化一個HandlerThread物件,引數是該執行緒的名稱;
  • 通過 HandlerThread.start()開啟執行緒;
  • 例項化一個Handler並傳入HandlerThread中的looper物件,使得與HandlerThread繫結;
  • 利用Handler即可執行非同步任務;
  • 當不需要HandlerThread時,通過HandlerThread.quit()/quitSafely()方法來終止執行緒的執行。
private HandlerThread myHandlerThread ;  
private Handler handler ;  
@Override  
protected void onCreate(Bundle savedInstanceState) {  
    super.onCreate(savedInstanceState);  
   setContentView(R.layout.activity_main);  
   //例項化HandlerThread
   myHandlerThread = new HandlerThread("myHandler") ;  
   //開啟HandlerThread
   myHandlerThread.start();  
   //將Handler物件與HandlerThread執行緒繫結
   handler =new Handler(myHandlerThread.getLooper()){  
       @Override  
        publicvoid handleMessage(Message msg) {  
           super.handleMessage(msg);  
            // 這裡接收Handler發來的訊息,執行在handler_thread執行緒中  
            //TODO...  
        }  
    };  
   
   //在主執行緒給Handler傳送訊息  
   handler.sendEmptyMessage(1) ;  
   new Thread(new Runnable() {  
       @Override  
        publicvoid run() {  
           //在子執行緒給Handler傳送資料  
           handler.sendEmptyMessage(2) ;  
        }  
    }).start();  
}  
@Override  
protected void onDestroy() {  
   super.onDestroy();  
   //終止HandlerThread執行
   myHandlerThread.quit() ;  
}  
複製程式碼

補充例項Android 多執行緒之HandlerThread 完全詳解

c.用途:

  • 進行序列非同步通訊
  • 構造IntentService

在之前學習Handler機制時知道在子執行緒使用Handler的方法,其實HandlerThread的出現使得這一過程變得更加簡便,更多解析見淺析HandlerThread


3.IntentService

a.IntentService是一個繼承自Service的抽象類

b.優點:

  • 相比於執行緒:由於是服務,優先順序比執行緒高,更不容易被系統殺死。因此較適合執行一些高優先順序的後臺任務。
  • 相比於普通Service:可自動建立子執行緒來執行任務,且任務執行完畢後自動退出

c.IntentService內部封裝了HandlerThread和Handler,工作原理:

  • IntentService.onCreate()裡建立一個Handle物件即HandlerThread,利用其內部的Looper會例項化一個ServiceHandler物件;
  • 任務請求的Intent會被封裝到Message並通過ServiceHandler傳送給Looper的MessageQueue,最終在HandlerThread中執行;
  • ServiceHandler.handleMessage()中會呼叫IntentService.onHandleIntent(),可在該方法中處理後臺任務的邏輯。

流程圖

圖片來源Android IntentService的使用和原始碼分析

d.使用方法:

  • 新建類並繼承IntentService,重寫onHandleIntent()方法,該方法:
    • 執行在:子執行緒,因此可以去處理一些耗時操作。
    • 作用:從Intent引數中區分具體的任務並執行這些任務
  • 在配置檔案中進行註冊。
  • 在活動中利用Intent實現IntentService的啟動:
Intent intent = new Intent(this, MyService.class);
intent.putExtra("xxx",xxx);  
startService(intent);//啟動服務
複製程式碼

具體例項見Service篇之IntentService

注意:無需手動停止服務,onHandleIntent()執行結束之後,IntentService會自動停止。

推薦閱讀Android多執行緒:IntentService用法&原始碼分析


三.執行緒池

1.優點

  • 重用執行緒池中的執行緒,避免執行緒的建立和銷燬帶來的效能消耗;
  • 有效控制執行緒池的最大併發數,避免大量的執行緒之間因互相搶佔系統資源而導致阻塞現象;
  • 進行執行緒管理,提供定時/迴圈間隔執行等功能。
  • 執行緒池的概念來源:Java中的Executor,它是一個介面。
  • 執行緒池的真正實現:ThreadPoolExecutor,提供一系列引數來配置執行緒池。
//構造引數
public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {
複製程式碼
  • corePoolSize:核心執行緒數
    • 預設情況下,核心執行緒會線上程中一直存活。
    • 當設定ThreadPoolExecutor的allowCoreThreadTimeOut屬性為
      • true:表示核心執行緒閒置超過超時時長,會被回收;
      • false:表示核心執行緒不會被回收,會線上程池中一直存活。
  • maximumPoolSize:最大執行緒數
    • 當活動執行緒數達到這個數值後,後續的任務將會被阻塞。
  • keepAliveTime:非核心執行緒超時時間
    • 超過這個時長,閒置的非核心執行緒就會被回收。
    • 當設定ThreadPoolExecutor的allowCoreThreadTimeTout屬性為true時,keepAliveTime對核心執行緒同樣有效。
  • unit:用於指定keepAliveTime引數的時間單位
    • 單位有:TimeUnit.MILLISECONDS、TimeUnit.SECONDS、TimeUnit.MINUTES等;
  • workQueue:任務佇列
    • 通過執行緒池的execute()方法提交的Runnable物件會儲存在這個引數中。
  • threadFactory:執行緒工廠,可建立新執行緒
    • 是個介面,只有一個方法Thread newThread(Runnable r)
  • handler:線上程池無法執行新任務時進行排程。

例項執行緒池的原理及實現

3.ThreadPoolExecutor的預設工作策略

  • 若程池中的執行緒數量未達到核心執行緒數,則會直接啟動一個核心執行緒執行任務。
  • 若執行緒池中的執行緒數量已達到或者超過核心執行緒數量,則任務會被插入到任務列表等待執行。
    • 若任務無法插入到任務列表中,往往由於任務列表已滿,此時如果
      • 執行緒數量未達到執行緒池最大執行緒數,則會啟動一個非核心執行緒執行任務;
      • 執行緒數量已達到執行緒池規定的最大值,則拒絕執行此任務,ThreadPoolExecutor會呼叫RejectedExecutionHandler的rejectedExecution方法來通知呼叫者。

4.ThreadPoolExecutor執行緒池的分類:

  • FixThreadPool
    • 含義:執行緒數量固定的執行緒池,所有執行緒都是核心執行緒,當執行緒空閒時不會被回收。
    • 特點:能快速響應外界請求。
  • CachedThreadPool
    • 含義:執行緒數量不定的執行緒池(最大執行緒數為Integer.MAX_VALUE),只有非核心執行緒,空閒執行緒有超時機制,超時回收。
    • 特點:適合於執行大量的耗時較少的任務
  • ScheduledThreadPool
    • 含義:核心執行緒數量固定,非核心執行緒數量不定
    • 特點:定時任務和固定週期的任務。
  • SingleThreadExecutor
    • 含義:只有一個核心執行緒,可確保所有的任務都在同一個執行緒中按順序執行。
    • 特點:無需處理執行緒同步問題。

推薦閱讀Java四種執行緒池的使用


希望這篇文章對你有幫助~

相關文章