今天,咱們細說Android下的多線程

楔子

蘇格拉底曾說過:「學會了多線程,你就學會了壓榨CPU,就好像資本家對無產階級作的那事同樣。」html

多線程是開發人員必不可少的技術點,也是初學者不太容易掌握好的一個難點。要想設計出優秀的程序,那必然須要合理的線程調度。今天就給你們細說下Android中與多線程相關的知識點,揭開多線程神祕的面紗。git

本篇文章僅介紹多線程的各類實現方式,不過多涉及深刻的基礎原理探究,達到「所見即所學,所學便可用」的效果。關於各類多線程原理的深刻探究,有機會放在後面的專欄逐一介紹。編程

1、多線程是什麼?我爲何要用多線程?

1.1 線程和進程的概念

按照操做系統中的描述,線程是CPU調度的最小單元,同時線程是一種有限的系統資源。而進程通常指一個執行單元,在PC和移動設備上指一個程序或者一個應用。一個進程能夠包含多個線程。bash

簡單點理解,一個Android APP就是一個進程,一個APP裏面有多個線程,咱們多線程編程的意義就是實現「一個APP多個線程」。網絡

有槓精可能會問,那我可不能夠一個APP多個進程?又可不能夠一個進程只有一個線程?多線程

我告訴你,能夠,均可以。併發

單線程的APP只包括Android的UI線程也是能運行的;一個APP多個進程也是能夠達到的,實現方式涉及到Android的IPC機制,這裏不細說。異步

1.2 爲何要使用多線程?

這裏槓精可能會說,那你單線程也能跑,我爲啥還要整多線程?ide

我告訴你,首先這句話從Android開發的角度來說,近似於一個假命題。由於谷歌爸爸如今強制規定了不能在UI線程進行耗時操做,必須放到子線程裏面去,除非你的程序不涉及耗時操做。究其緣由,是由於在UI線程進行耗時操做的話,給用戶的使用體驗就是界面「卡頓」。同時,若是UI線程被阻塞超過必定時間會觸發ANR(Application Not Responding)錯誤。oop

從底層的角度來說,多線程可使得整個環境可以異步執行,這有助於防止浪費CPU時鐘週期從而提升效率。換言之,多線程能更充分的利用CPU資源,從而提升程序的運行效率。

2、那我怎麼進行多線程編程?

2.1 Thread類和Runnable接口

要想定義一個線程只須要新建一個類繼承自Thread,而後重寫父類的run方法便可

class MyThread extends Thread {
    @Override
    public void run() {
        doSomething();
    }
}

//在須要的時候啓動線程
new MyThread().start();
複製代碼

優化一下?

咱們能夠不必繼承整個Thread類,只實現Runnable接口就行了

class MyThread implements Runnable {
    @Override
    public void run() {
        doSomething()
    }
}

//啓動線程
MyThread myThread = new MyThread();
new Thread(myThread).start();
複製代碼

那我不想專門再寫一個線程類怎麼辦?可使用匿名類

new Thread(new Runnable() {
    @Override
    public void run() {
        doSomething();
    }
}).start();
複製代碼

2.2 線程池

2.2.1 線程池的意義

既然我都會用Runnable接口來建立線程了,還要線程池幹啥?其實否則,隨意建立線程的操做在實際開發中是極爲不推薦的。爲啥?由於線程也是一種資源,反覆的建立和銷燬線程會帶來必定性能上的額外開銷。與其相比,線程池主要有如下幾個優勢:

  • 重用線程池中的線程,避免由於線程的建立和銷燬所帶來的性能開銷
  • 能有效控制線程池的最大併發數,避免大量的線程之間因相互搶佔系統資源而致使的阻塞現象
  • 可以對線程進行簡單的管理,並提供定時執行以及指定間隔循環執行等功能

2.2.2 線程池的結構和原理

一個完整的線程池應該有這麼幾個組成部分

  • 核心線程
  • 任務隊列
  • 非核心線程

當咱們經過線程池執行異步任務的時候,實際上是依次進行了下面的流程

  1. 檢查覈心線程數是否到達最大值,不然建立新的核心線程執行任務,是則進行下一步
  2. 檢查任務隊列是否已滿,不然將任務添加到任務隊列中,是則進行下一步
  3. 檢查非核心線程數是否到達最大值,不然建立新的非核心線程執行任務,是則說明這個線程池已經飽和了,執行飽和策略。默認的飽和策略是拋出RejectedExecutionException異常

下面手搓一個線程池的實現

//CPU核心數
private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
//核心線程數
private static final int CORE_POOL_SIZE = CPU_COUNT + 1;
//最大線程數
private static final int MAX_POOL_SIZE = CPU_COUNT * 2 + 1;
//非核心線程閒置的超時時間
private static final int KEEP_ALIVE_TIME = 1;
//任務隊列
private static final BlockingQueue<Runnable> sPoolWorkQueue =
        new LinkedBlockingQueue<Runnable>(128);
//線程池
private ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(CORE_POOL_SIZE,
        MAX_POOL_SIZE, KEEP_ALIVE_TIME, TimeUnit.SECONDS, sPoolWorkQueue);

private void fun(){
    Runnable runnable = new Runnable() {
        @Override
        public void run() {
            //子線程處理耗時操做
            doSomething();
        }
    };
    poolExecutor.execute(runnable);
}
複製代碼

這樣咱們就實現了一個簡單的線程池,核心線程數爲CPU數量+1,非核心線程數爲CPU數量*2+1,非核心線程的閒置時間爲1秒,任務隊列的大小爲128。

線程池還有具體的好幾種分類和相應不一樣的實現方式,這裏再也不細說。

2.3 Handler

有朋友可能會說,你講的這些都是Java多線程裏面的東西,能不能整點咱Android特有的?OK,如今進入專業時間。

Handler是Android提供的一種異步消息處理機制,要學會使用Handler咱們首先來了解下消息處理四兄弟:

  • Message
  • Handler
  • MessageQueue
  • Looper

Handler能夠幫助咱們實如今不一樣的線程之間傳遞消息,這裏的Message就是消息本體,也就是咱們想要傳遞的那個東西。

Handler在這裏扮演的角色是消息處理者,它的主要做用是發送和處理消息。

MessageQueue是一個消息隊列,Handler發送過來的消息會放在這個隊列裏面,每一個線程只會有一個MessageQueue對象。

Looper是線程中消息隊列的管家,它會無限循環運行,每發現MessageQueue中存在一條消息,它就會把消息取出而後發送給Handler。每個線程也只能有一個Looper對象。

好了,基本原理已經瞭解,如今咱們來反手搓一個Handler

private static final int FLAG = 1;

private Handler mHandler = new Handler(){
    @Override
    public void handleMessage(@NonNull Message msg) {
        if (FLAG == msg.what){
            //這裏已經回到主線程了
            doSomething();
        }
    }
};

private void fun(){
    new Thread(new Runnable() {
        @Override
        public void run() {
            //子線程發送消息
            Message message = new Message();
            message.what = FLAG;
            mHandler.sendMessage(message);
        }
    }).start();
}
複製代碼

2.4 AsyncTask

除了Handler之外,谷歌爸爸還給咱們提供AsyncTask來進行線程的切換。AsyncTask是一種輕量級的異步任務,它能夠在線程池中執行後臺任務,而後把執行的進度和最終結果傳遞給主線程。從實現原理上來說,AsyncTask是對Thread和Handle的再次封裝。

AsyncTask自己是一個抽象的泛型類,有四個親兒子:

  • onPreExecute()
  • doInBackground(Params...params)
  • onProgressUpdate(Progress...values)
  • onPostExecute(Result result)

最早執行的是方法是onPreExecute()方法,位於主線程中,通常用來作一些準備工做。

而後執行doInBackground()方法,位於線程池中,用來執行異步任務,params表示異步任務的輸入參數。這個方法須要返回結果給onPostExecute()方法。

onProgressUpdate()方法在主線程中執行,當後臺任務的執行進度發生變化時這個方法會被調用。

onPostExecute()方法在最後異步任務完成以後會被調用,位於主線程中,result參數是後臺任務的返回值,即doInBackground()的返回值。

OK,基本原理已經瞭解了,如今咱們來手搓一個AsyncTask

class DownloadTask extends AsyncTask<Void,Integer,Boolean> {

    @Override
    protected void onPreExecute() {
        //這裏咱們使用了一個顯示進度的Dialog,具體實現不表
        progressDialog.show();
    }

    @Override
    protected Boolean doInBackground(Void... voids) {
        try {
            while (true){
                //調用咱們的doDownload下載方法,具體實現不表
                int downloadPercent = doDownload();
                //使用publishProgress方法來更新執行的進度
                publishProgress(downloadPercent);
                if (downloadPercent >= 100)
                    break;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return true;
    }

    @Override
    protected void onProgressUpdate(Integer... values) {
        //更新下載進度
        progressDialog.setMessage("Download "+values[0]+"%");
    }

    @Override
    protected void onPostExecute(Boolean aBoolean) {
        //下載完成
        progressDialog.dismiss();
    }
}
複製代碼

這裏咱們建立了一個Download類繼承自AsyncTask,有三個泛型,void表示不須要給後臺任務傳入參數,Integer表示用整數類型來做爲進度顯示的單位,Boolean表示用布爾類型來反饋後臺任務的執行結果。

要讓咱們的這個AsyncTask跑起來也很簡單,只須要執行:

new DownloadTask().execute();

2.5 IntentService

IntentService是一種特殊的Service,它繼承了Service而且是一個抽象類,咱們能夠建立它的子類來使用。IntentService也能夠用於執行後臺的耗時任務,而且當任務執行完畢以後它會自動中止。

IntentService由於是服務的緣由,因此和單純的線程相比它的優先級要高不少,從而更不容易被系統殺死。

IntentService的內部實現是封裝了HandlerThread和Handler,使用的話要遵循Service的使用方法,這裏先略事後面有機會在Service的專欄裏面再詳細介紹。

2.6 RxJava

有槓精可能會說,你講的這些方法,一個比一個長,一個比一個複雜,就不能整個簡單又粗暴的東西?

這個時候就須要祭出神兵利器RxJava了。

2.6.1 RxJava又是個啥?

其實網絡上RxJava的入門文章多如過江之鯽,這裏不打算過多的深刻介紹。RxJava是一種響應式編程,你們不是很明白的話能夠粗暴的理解爲更優雅的多線程實現便可。

2.6.2 那怎麼操做RxJava?

先手搓一個RxJava的普通實現方式

private void fun(){
    Observable<Integer> observable = Observable.create(new ObservableOnSubscribe<Integer>() {
        @Override
        public void subscribe(ObservableEmitter<Integer> emitter) throws Exception {
            emitter.onNext(1);
        }
    });

    observable.subscribeOn(Schedulers.io())     //表示在io線程執行訂閱
            .observeOn(AndroidSchedulers.mainThread())  //表示在主線程接收訂閱
            .subscribe(new Observer<Integer>() {
                @Override
                public void onSubscribe(Disposable d) {
                    //接收訂閱以前調用
                }

                @Override
                public void onNext(Integer integer) {
                    //接收訂閱成功調用
                    doSomething();
                }

                @Override
                public void onError(Throwable e) {
                    //接收訂閱出錯調用
                }

                @Override
                public void onComplete() {
                    //接收訂閱完成調用
                }
            });
}
複製代碼

emmmmm看起來好像仍是挺複雜的啊,能不能再整簡單點?

OK,鏈式調用加lambda安排上

private void fun() {
    Observable.create(emitter -> emitter.onNext(1))
            .subscribeOn(Schedulers.io())
            .observeOn(AndroidSchedulers.mainThread())
            .subscribe(integer -> {
                //接收訂閱成功
                doSomething();
            }, throwable -> {});
}
複製代碼

嗯......有內味了。

這串代碼咱們是發送了一個Integer類型的數據;

subscribeOn()指定了咱們發送的線程是在後臺的io線程,就能夠理解爲一個子線程;

observeOn指定了咱們接收的線程爲主線程;

subscribe只接收成功的消息,至關於上面的OnNext()方法,本質上是咱們在這裏建立了一個Comsumer對象來接收;

throwable在接收失敗的時候調用,至關於上面的onError()方法。

RxJava有多達幾十種的操做符,靈活運用能實現各類不一樣的異步任務,這裏就再也不花大量的篇幅詳細介紹了,有興趣的朋友能夠去查看ReactiveX中文文檔

2.7 RxKotlin

RxKotlin能夠理解爲RxJava在Kotlin上的一個變種,原理都是同樣的,只是操做語言變成了Kotlin,而後封裝了一下使得能夠更優雅的調用,這裏給你們一個具體的實現案例,再也不過多講解。

private fun test() {
    Observable.create<Int> { 1 }
            .subscribeOn(Schedulers.io())
            .observeOn(AndroidSchedulers.mainThread())
            .subscribeBy(
                    onNext = {},
                    onError = {}
            )
}
複製代碼

2.8 Kotlin協程

協程其實和上面所說的線程並非一個概念,協程是什麼?根據官方文檔的描述,協程本質上是輕量級的線程。既然是輕量,那說明協程的資源消耗和性能等方面和線程比起來應該是有優點的。那這樣看來咱們之前使用多線程實現的異步功能,如今基本上均可以用協程來替代了。

協程是一個全新的東西,介於篇幅這裏就不展開講解了,後面會專門寫介紹協程的文章。

3、總結

今天總結了Android平臺上實現多線程的幾種方式,但願能給到須要的朋友一些幫助。

有不對之處還望各位同僚雅正。

參考資料:

  1. 《Java徹底參考手冊 第八版》
  2. 《第一行代碼 第二版》
  3. 《Android進階之光》
  4. 《Android開發藝術探究》
  5. ReactiveX中文文檔
相關文章
相關標籤/搜索