最美情侣中文字幕电影,在线麻豆精品传媒,在线网站高清黄,久久黄色视频

歡迎光臨散文網(wǎng) 會員登陸 & 注冊

Android多線程開發(fā)總結(jié)

2020-01-18 22:00 作者:啵嘰玉桂狗勾  | 我要投稿

up打字都手疼,求點個贊。

Android多線程

多線程開發(fā)在Android技術中非常重要,能否熟練掌握這些技術是衡量一個工程師技術水平能力的一個重要標準,也是決定能否開發(fā)出高效優(yōu)質(zhì)應用的前提條件。下面將分別展開描述以及對比,并結(jié)合實際工作場合分析優(yōu)劣。主要有以下幾種:


Thread

Handler

HandlerThread

IntentService

ThreadPool

1 Thread(線程)

1.1 定義

一個基本的CPU執(zhí)行單元 & 程序執(zhí)行流的最小單元


組成:線程ID + 程序計數(shù)器 + 寄存器集合 + 堆棧。比進程更小的可獨立運行的基本單位,可理解為:輕量級進程。線程自己不擁有系統(tǒng)資源,與其他線程共享進程所擁有的全部資源。


1.2 與進程的區(qū)別

進程是資源分配的最小單位,線程是程序執(zhí)行的最小單位。

進程有自己的獨立地址空間,每啟動一個進程,系統(tǒng)就會為它分配地址空間,建立數(shù)據(jù)表來維護代碼段、堆棧段和數(shù)據(jù)段,這種操作非常昂貴。而線程是共享進程中的數(shù)據(jù)的,使用相同的地址空間,因此CPU切換一個線程的花費遠比進程要小很多,同時創(chuàng)建一個線程的開銷也比進程要小很多。

線程之間的通信更方便,同一進程下的線程共享全局變量、靜態(tài)變量等數(shù)據(jù),而進程之間的通信需要以通信的方式(IPC)進行。不過如何處理好同步與互斥是編寫多線程程序的難點。

但是多進程程序更健壯,多線程程序只要有一個線程死掉,整個進程也死掉了,而一個進程死掉并不會對另外一個進程造成影響,因為進程有自己獨立的地址空間。

1.3 線程分類

線程主要分為:守護線程、非守護線程(用戶線程)


1.3.1 守護線程

定義:守護用戶線程的線程,即在程序運行時為其他線程提供一種通用服務

常見:如 垃圾回收線程

設置方式:

//設置該線程為守護線程

thread.setDaemon(true);

1

2

1.3.2 非守護線程(用戶線程)

主要包括:主線程 、子線程(工作線程)。


a. 主線程(UI線程)


定義:Android系統(tǒng)在程序啟動時會自動啟動一條主線程

作用:處理四大組件與用戶進行交互的事情(如UI、界面交互相關)

注:因為用戶隨時會與界面發(fā)生交互,因此主線程任何時候都必須保持很高的響應速度,所以主線程不允許進行耗時操作,否則會出現(xiàn)ANR

b. 子線程(工作線程)


定義:手動創(chuàng)建的線程

作用:耗時的操作(網(wǎng)絡請求、I/O操作等)

1.3.3 守護線程 與 非守護線程的區(qū)別

區(qū)別:虛擬機是否已退出:

當所有用戶線程結(jié)束時,因為沒有守護的必要,所以守護線程也會終止,虛擬機也同樣退出;

反過來,只要任何用戶線程還在運行,守護線程就不會終止,虛擬機就不會退出

1.3 線程優(yōu)先級

1.3.1 表示

線程優(yōu)先級分為10個級別,分別用Thread類常量表示。


// 譬如:

Thread.MIN_PRIORITY // 優(yōu)先級1

Thread.MAX_PRIORITY // 優(yōu)先級10

1.3.2 設置

通過方法setPriority(int grade)進行優(yōu)先級設置

默認線程優(yōu)先級是5,即 Thread.NORM_PRIORITY


1.4 多線程 - 介紹

多個線程同時進行,即多個任務同時進行


其實,計算機任何特定時刻只能執(zhí)行一個任務;

多線程只是一種錯覺:只是因為JVM快速調(diào)度資源來輪換線程,使得線程不斷輪流執(zhí)行,所以看起來好像在同時執(zhí)行多個任務而已


1.5 線程調(diào)度

1.5.1 調(diào)度方式

當系統(tǒng)存在大量線程時,系統(tǒng)會通過時間片輪轉(zhuǎn)的方式調(diào)度線程,因此線程不可能做到絕對的并發(fā)

處于就緒狀態(tài)(Runnable)的線程都會進入到線程隊列中等待CPU資源

同一時刻在線程隊列中可能有很多個

在采用時間片的系統(tǒng)中,每個線程都有機會獲得CPU的資源以便進行自身的線程操作;當線程使用CPU資源的時間到后,即時線程沒有完成自己的全部操作,JVM也會中斷當前線程的執(zhí)行,把CPU資源的使用權切換給下一個隊列中等待的線程。

被中斷的線程將等待CPU資源的下一次輪回,然后從中斷處繼續(xù)執(zhí)行


1.5.2 調(diào)度優(yōu)先級

Java虛擬機(JVM)中的線程調(diào)度器負責管理線程,并根據(jù)以下規(guī)則進行調(diào)度:


根據(jù)線程優(yōu)先級(高-低),將CPU資源分配給各線程

具備相同優(yōu)先級的線程以輪流的方式獲取CPU資源

示例

存在A、B、C、D四個線程,其中:A和B的優(yōu)先級高于C和D(A、B同級,C、D同級,那么JVM將先以輪流的方式調(diào)度A、B,直到A、B線程死亡,再以輪流的方式調(diào)度C、D


1.6 線程的使用

繼承Thread類

// 步驟1:創(chuàng)建線程類 (繼承自Thread類)

class MyThread extends Thread{

// 步驟2:復寫run(),內(nèi)容 = 定義線程行為

? ? @Override

? ? public void run(){

? ? ... // 定義的線程行為

? ? }

}

// 步驟3:創(chuàng)建線程對象,即 實例化線程類

?MyThread mt=new MyThread(“線程名稱”);

// 步驟4:通過 線程對象 控制線程的狀態(tài),如 運行、睡眠、掛起? / 停止

// 此處采用 start()開啟線程

mt.start();

配合Runnable接口創(chuàng)建線程

// 步驟1:創(chuàng)建線程輔助類,實現(xiàn)Runnable接口

class MyThread implements Runnable{

? ?....

? ?@Override

// 步驟2:復寫run(),定義線程行為

? ?public void run(){


? ?}

}

// 步驟3:創(chuàng)建線程輔助對象,即 實例化 線程輔助類

MyThread mt = new MyThread();

// 步驟4:創(chuàng)建線程對象,即 實例化線程類;線程類 = Thread類;

// 創(chuàng)建時通過Thread類的構造函數(shù)傳入線程輔助類對象

// 原因:Runnable接口并沒有任何對線程的支持,我們必須創(chuàng)建線程類(Thread類)的實例,從Thread類的一個實例內(nèi)部運行

Thread td=new Thread(mt);

// 步驟5:通過 線程對象 控制線程的狀態(tài),如 運行、睡眠、掛起? / 停止

// 當調(diào)用start()方法時,線程對象會自動回調(diào)線程輔助類對象的run(),從而實現(xiàn)線程操作

td.start();

匿名類創(chuàng)建線程

new Thread(): 在阿里開發(fā)手冊中明確禁止使用這種方式開啟新線程,主要是因為新線程這樣開啟之后無法主動停止,只適合執(zhí)行耗時短的輕量級任務


// 步驟1:采用匿名類,直接 創(chuàng)建 線程類的實例

?new Thread("線程名稱") {

? // 步驟2:復寫run(),內(nèi)容 = 定義線程行為

? ? ?@Override

? ? ?public void run() {? ? ? ?

? // 步驟3:通過 線程對象 控制線程的狀態(tài),如 運行、睡眠、掛起? / 停止? ?

? ?}

?}.start();

1.7 new Thread()的缺點

在實際多線程開發(fā)中,一般不建議直接用new Thread(),性能開銷達,不好管理,而要使用下面即將提到的其他多線程技術。


每次new Thread()耗費性能

調(diào)用new Thread()創(chuàng)建的線程缺乏管理,被稱為野線程,而且可以無限制創(chuàng)建,之間相互競爭,會導致過多占用系統(tǒng)資源導致系統(tǒng)癱瘓。

不利于擴展,比如如定時執(zhí)行、定期執(zhí)行、線程中斷

2 Handler

2.1 定義

一套 Android 消息傳遞機制


2.2 作用

在多線程的應用場景中,將工作線程中需更新UI的操作信息 傳遞到 UI主線程,從而實現(xiàn) 工作線程對UI的更新處理,最終實現(xiàn)異步消息的處理



2.3 意義

多個線程并發(fā)更新UI的同時 保證線程安全


2.4 相關概念

Handler 、 Looper 、Message 這三者都與Android異步消息處理線程相關的概念。那么什么叫異步消息處理線程呢?



異步消息處理線程啟動后會進入一個無限的循環(huán)體之中,每循環(huán)一次,從其內(nèi)部的消息隊列中取出一個消息,然后回調(diào)相應的消息處理函數(shù),執(zhí)行完成一個消息后則繼續(xù)循環(huán)。若消息隊列為空,線程則會阻塞等待。

那么Android消息機制主要是指Handler的運行機制,Handler運行需要底層的MessageQueue和Looper支撐。其中MessageQueue采用的是單鏈表的結(jié)構,Looper可以叫做消息循環(huán)。由于MessageQueue只是一個消息存儲單元,不能去處理消息,而Looper就是專門來處理消息的,Looper會以無限循環(huán)的形式去查找是否有新消息,如果有的話,就處理,否則就一直等待著。

我們知道,Handler創(chuàng)建的時候會采用當前線程的Looper來構造消息循環(huán)系統(tǒng),需要注意的是,線程默認是沒有Looper的,如果需要使用Handler就必須為線程創(chuàng)建Looper,因為默認的UI主線程,也就是ActivityThread,ActivityThread被創(chuàng)建的時候就會初始化Looper,這也是在主線程中默認可以使用Handler的原因。如果想讓該線程具有消息隊列和消息循環(huán),并具有消息處理機制,就需要在線程中首先調(diào)用Looper.prepare()來創(chuàng)建消息隊列,然后調(diào)用Looper.loop()進入消息循環(huán)。如以下代碼所示:

public class LopperThread extends Thread{

? ? public Handler mHandler;

? ??

? ? @Override

? ? public void run(){

? ? ? ? Looper.prepare();

? ? ? ? mHandler = new Handler(){

? ? ? ??

? ? ? ? ? ? @Override

? ? ? ? ? ? public void handleMessage(Message msg) {

? ? ? ? ? ? ? ? super.handleMessage(msg);

? ? ? ? ? ? ? ? //處理消息隊列

? ? ? ? ? ? }

? ? ? ? };

? ? ? ? Looper.loop();

? ? }

}

2.5 使用方式

Handler.sendMessage

在該使用方式中,又分為2種:新建Handler子類(內(nèi)部類)、匿名 Handler子類


/**?

? * 方式1:新建Handler子類(內(nèi)部類)

*/


// 步驟1:自定義Handler子類(繼承Handler類) & 復寫handleMessage()方法

class mHandler extends Handler {


? ? // 通過復寫handlerMessage() 從而確定更新UI的操作

? ? @Override

? ? public void handleMessage(Message msg) {

? ? ?...// 需執(zhí)行的UI操作

? ? ? ??

? ? }

}


// 步驟2:在主線程中創(chuàng)建Handler實例

private Handler mhandler = new mHandler();


// 步驟3:創(chuàng)建所需的消息對象

Message msg = Message.obtain(); // 實例化消息對象

msg.what = 1; // 消息標識

msg.obj = "AA"; // 消息內(nèi)容存放


// 步驟4:在工作線程中 通過Handler發(fā)送消息到消息隊列中

// 可通過sendMessage() / post()

// 多線程可采用AsyncTask、繼承Thread類、實現(xiàn)Runnable

mHandler.sendMessage(msg);


// 步驟5:開啟工作線程(同時啟動了Handler)

// 多線程可采用AsyncTask、繼承Thread類、實現(xiàn)Runnable


/**?

* 方式2:匿名內(nèi)部類

*/

// 步驟1:在主線程中 通過匿名內(nèi)部類 創(chuàng)建Handler類對象

private Handler mhandler = new? Handler(){

? ? // 通過復寫handlerMessage()從而確定更新UI的操作

@Override

public void handleMessage(Message msg) {

? ? ...// 需執(zhí)行的UI操作

}

};


// 步驟2:創(chuàng)建消息對象

Message msg = Message.obtain(); // 實例化消息對象

msg.what = 1; // 消息標識

msg.obj = "AA"; // 消息內(nèi)容存放


// 步驟3:在工作線程中 通過Handler發(fā)送消息到消息隊列中

// 多線程可采用AsyncTask、繼承Thread類、實現(xiàn)Runnable

mHandler.sendMessage(msg);


// 步驟4:開啟工作線程(同時啟動了Handler)

// 多線程可采用AsyncTask、繼承Thread類、實現(xiàn)Runnable

Handler.post

// 步驟1:在主線程中創(chuàng)建Handler實例

private Handler mhandler = new mHandler();


// 步驟2:在工作線程中 發(fā)送消息到消息隊列中 & 指定操作UI內(nèi)容

// 需傳入1個Runnable對象

mHandler.post(new Runnable() {

@Override

public void run() {

? ?... // 需執(zhí)行的UI操作?

}

});


// 步驟3:開啟工作線程(同時啟動了Handler)

// 多線程可采用AsyncTask、繼承Thread類、實現(xiàn)Runnable

2.6 Handler內(nèi)存泄漏

1、內(nèi)存泄露的定義:本該被回收的對象不能被回收而停留在堆內(nèi)存中

2、內(nèi)存泄露出現(xiàn)的原因:當一個對象已經(jīng)不再被使用時,本該被回收但卻因為有另外一個正在使用的對象持有它的引用從而導致它不能被回收,這就導致了內(nèi)存泄漏

3、主線程的Looper對象的生命周期 = 該應用程序的生命周期

4、在Java中,非靜態(tài)內(nèi)部類 & 匿名內(nèi)部類都默認持有 外部類的引用


在Handler消息隊列 還有未處理的消息 / 正在處理消息時,此時若需銷毀外部類MainActivity,但由于引用關系,垃圾回收器(GC)無法回收MainActivity,從而造成內(nèi)存泄漏。


2.6.1 解決方案

解決方案1:靜態(tài)內(nèi)部類+弱引用

1、將Handler的子類設置成 靜態(tài)內(nèi)部類,同時,還可加上 使用WeakReference弱引用持有Activity實例。原因:弱引用的對象擁有短暫的生命周期。在垃圾回收器線程掃描時,一旦發(fā)現(xiàn)了只具有弱引用的對象,不管當前內(nèi)存空間足夠與否,都會回收它的內(nèi)存。

2、為了保證Handler中消息隊列中的所有消息都能被執(zhí)行,此處推薦使用解決方案1解決內(nèi)存泄露問題,即靜態(tài)內(nèi)部類 + 弱引用的方式。


public class MainActivity extends AppCompatActivity {


? ? public static final String TAG = "carson:";

? ? private Handler showhandler;


? ? // 主線程創(chuàng)建時便自動創(chuàng)建Looper & 對應的MessageQueue

? ? // 之后執(zhí)行Loop()進入消息循環(huán)

? ? @Override

? ? protected void onCreate(Bundle savedInstanceState) {

? ? ? ? super.onCreate(savedInstanceState);

? ? ? ? setContentView(R.layout.activity_main);


? ? ? ? //1. 實例化自定義的Handler類對象

? ? ? ?// a. 此處并無指定Looper,故自動綁定當前線程(主線程)的Looper、MessageQueue;

? ? ? ?// b. 定義時需傳入持有的Activity實例(弱引用)

? ? ? ? showhandler = new FHandler(this);

? ? ? ??

? ? ? ? // 2. 啟動子線程

? ? ? ? new Thread() {

? ? ? ? ? ? @Override

? ? ? ? ? ? public void run() {

? ? ? ? ? ? ? ? try {

? ? ? ? ? ? ? ? ? ? Thread.sleep(5000);

? ? ? ? ? ? ? ? } catch (InterruptedException e) {

? ? ? ? ? ? ? ? ? ? e.printStackTrace();

? ? ? ? ? ? ? ? }

? ? ? ? ? ? ? ? // a. 定義要發(fā)送的消息

? ? ? ? ? ? ? ? Message msg = Message.obtain();

? ? ? ? ? ? ? ? msg.what = 2;// 消息標識

? ? ? ? ? ? ? ? msg.obj = "BB";// 消息存放

? ? ? ? ? ? ? ? // b. 傳入主線程的Handler & 向其MessageQueue發(fā)送消息

? ? ? ? ? ? ? ? showhandler.sendMessage(msg);

? ? ? ? ? ? }

? ? ? ? }.start();

? ? }


? ? // 分析1:自定義Handler子類

? ? // 設置為:靜態(tài)內(nèi)部類

? ? private static class FHandler extends Handler{

? ? ? ? // 定義 弱引用實例

? ? ? ? private WeakReference<Activity> reference;

? ? ? ??

? ? ? ? // 在構造方法中傳入需持有的Activity實例

? ? ? ? public FHandler(Activity activity) {

? ? ? ? ? ? // 使用WeakReference弱引用持有Activity實例

? ? ? ? ? ? reference = new WeakReference<Activity>(activity); }


? ? ? ? // 通過復寫handlerMessage() 從而確定更新UI的操作

? ? ? ? @Override

? ? ? ? public void handleMessage(Message msg) {

? ? ? ? ? ? switch (msg.what) {

? ? ? ? ? ? ? ? case 1:

? ? ? ? ? ? ? ? ? ? Log.d(TAG, "收到線程1的消息");

? ? ? ? ? ? ? ? ? ? break;

? ? ? ? ? ? ? ? case 2:

? ? ? ? ? ? ? ? ? ? Log.d(TAG, " 收到線程2的消息");

? ? ? ? ? ? ? ? ? ? break;

? ? ? ? ? ? }

? ? ? ? }

? ? }

}

解決方案2:當外部類結(jié)束生命周期時,清空Handler內(nèi)消息隊列

當 外部類(此處以Activity為例)結(jié)束生命周期時,清除 Handler消息隊列里的所有消息。


@Override

protected void onDestroy() {

? ? super.onDestroy();

? ? // 外部類Activity生命周期結(jié)束時,同時清空消息隊列 & 結(jié)束Handler生命周期

? ? mHandler.removeCallbacksAndMessages(null);

}

3 AsyncTask(Handler+線程池(默認串行))

1、在工作線程中執(zhí)行任務,如 耗時任務

2、實現(xiàn)工作線程 & 主線程(UI線程)之間的通信,即:將工作線程的執(zhí)行結(jié)果傳遞給主線程,從而在主線程中執(zhí)行相關的UI操作

3、從而保證線程安全


3.1 優(yōu)點

方便實現(xiàn)異步通信

不需使用 “任務線程(如繼承Thread類)+ Handler”的復雜組合


節(jié)省資源

采用線程池的緩存線程 + 復用線程,避免了頻繁創(chuàng)建 & 銷毀線程所帶來的系統(tǒng)資源開銷


3.2 缺點

實現(xiàn)比較繁瑣,代碼可讀性差

實現(xiàn)一個AsyncTask比較繁瑣,而且往往不用的業(yè)務需要實現(xiàn)不同的AsyncTask,導致代碼可讀性差一點,實際上項目用得不多,有其他更好的替代方案。


3.3 實例

public class MainActivity extends AppCompatActivity {

? ? // 線程變量

? ? MyTask mTask;

? ? // 主布局中的UI組件

? ? Button button,cancel; // 加載、取消按鈕

? ? TextView text; // 更新的UI組件

? ? ProgressBar progressBar; // 進度條

?

? ? /**

? ? ?* 步驟1:創(chuàng)建AsyncTask子類

? ? ?* 注:

? ? ?*? ?a. 繼承AsyncTask類

? ? ?*? ?b. 為3個泛型參數(shù)指定類型;若不使用,可用java.lang.Void類型代替

? ? ?*? ? ? 此處指定為:輸入?yún)?shù) = String類型、執(zhí)行進度 = Integer類型、執(zhí)行結(jié)果 = String類型

? ? ?*? ?c. 根據(jù)需求,在AsyncTask子類內(nèi)實現(xiàn)核心方法

? ? ?*/

? ? private class MyTask extends AsyncTask<String, Integer, String> {

? ? ? ? // 方法1:onPreExecute()

? ? ? ? // 作用:執(zhí)行 線程任務前的操作

? ? ? ? @Override

? ? ? ? protected void onPreExecute() {

? ? ? ? ? ? text.setText("加載中");

? ? ? ? ? ? // 執(zhí)行前顯示提示

? ? ? ? }

? ? ? ??

? ? ? ? // 方法2:doInBackground()

? ? ? ? // 作用:接收輸入?yún)?shù)、執(zhí)行任務中的耗時操作、返回 線程任務執(zhí)行的結(jié)果

? ? ? ? // 此處通過計算從而模擬“加載進度”的情況

? ? ? ? @Override

? ? ? ? protected String doInBackground(String... params) {

? ? ? ? ? ? try {

? ? ? ? ? ? ? ? int count = 0;

? ? ? ? ? ? ? ? int length = 1;

? ? ? ? ? ? ? ? while (count<99) {

? ? ? ? ? ? ? ? ? ? count += length;

? ? ? ? ? ? ? ? ? ? // 可調(diào)用publishProgress()顯示進度, 之后將執(zhí)行onProgressUpdate()

? ? ? ? ? ? ? ? ? ? publishProgress(count);

? ? ? ? ? ? ? ? ? ? // 模擬耗時任務

? ? ? ? ? ? ? ? ? ? Thread.sleep(50);

? ? ? ? ? ? ? ? }

? ? ? ? ? ? }catch (InterruptedException e) {

? ? ? ? ? ? ? ? e.printStackTrace();

? ? ? ? ? ? }

? ? ? ? ? ? return null;

? ? ? ? }


? ? ? ? // 方法3:onProgressUpdate()

? ? ? ? // 作用:在主線程 顯示線程任務執(zhí)行的進度

? ? ? ? @Override

? ? ? ? protected void onProgressUpdate(Integer... progresses) {

? ? ? ? ? ? progressBar.setProgress(progresses[0]);

? ? ? ? ? ? text.setText("loading..." + progresses[0] + "%");

? ? ? ? }


? ? ? ? // 方法4:onPostExecute()

? ? ? ? // 作用:接收線程任務執(zhí)行結(jié)果、將執(zhí)行結(jié)果顯示到UI組件

? ? ? ? @Override

? ? ? ? protected void onPostExecute(String result) {

? ? ? ? ? ? // 執(zhí)行完畢后,則更新UI

? ? ? ? ? ? text.setText("加載完畢");

? ? ? ? }


? ? ? ? // 方法5:onCancelled()

? ? ? ? // 作用:將異步任務設置為:取消狀態(tài)

? ? ? ? @Override

? ? ? ? protected void onCancelled() {

? ? ? ? ? ? text.setText("已取消");

? ? ? ? ? ? progressBar.setProgress(0);

? ? ? ? }

? ? }


? ? @Override

? ? protected void onCreate(Bundle savedInstanceState) {

? ? ? ? super.onCreate(savedInstanceState);

? ? ? ? // 綁定UI組件

? ? ? ? setContentView(R.layout.activity_main);

? ? ? ? button = (Button) findViewById(R.id.button);

? ? ? ? cancel = (Button) findViewById(R.id.cancel);

? ? ? ? text = (TextView) findViewById(R.id.text);

? ? ? ? progressBar = (ProgressBar) findViewById(R.id.progress_bar);

? ? ? ? /**

? ? ? ? ?* 步驟2:創(chuàng)建AsyncTask子類的實例對象(即 任務實例)

? ? ? ? ?* 注:AsyncTask子類的實例必須在UI線程中創(chuàng)建

? ? ? ? ?*/

? ? ? ? mTask = new MyTask();


? ? ? ? // 加載按鈕按按下時,則啟動AsyncTask

? ? ? ? // 任務完成后更新TextView的文本

? ? ? ? button.setOnClickListener(new View.OnClickListener() {

? ? ? ? ? ? @Override

? ? ? ? ? ? public void onClick(View v) {

? ? ? ? ? ? ? ? /**

? ? ? ? ? ? ? ? ?* 步驟3:手動調(diào)用execute(Params... params) 從而執(zhí)行異步線程任務

? ? ? ? ? ? ? ? ?* 注:

? ? ? ? ? ? ? ? ?*? ? a. 必須在UI線程中調(diào)用

? ? ? ? ? ? ? ? ?*? ? b. 同一個AsyncTask實例對象只能執(zhí)行1次,若執(zhí)行第2次將會拋出異常

? ? ? ? ? ? ? ? ?*? ? c. 執(zhí)行任務中,系統(tǒng)會自動調(diào)用AsyncTask的一系列方法:onPreExecute() 、doInBackground()、onProgressUpdate() 、onPostExecute()

? ? ? ? ? ? ? ? ?*? ? d. 不能手動調(diào)用上述方法

? ? ? ? ? ? ? ? ?*/

? ? ? ? ? ? ? ? mTask.execute();

? ? ? ? ? ? }

? ? ? ? });


? ? ? ? cancel = (Button) findViewById(R.id.cancel);

? ? ? ? cancel.setOnClickListener(new View.OnClickListener() {

? ? ? ? ? ? @Override

? ? ? ? ? ? public void onClick(View v) {

? ? ? ? ? ? ? ? // 取消一個正在執(zhí)行的任務,onCancelled方法將會被調(diào)用

? ? ? ? ? ? ? ? mTask.cancel(true);

? ? ? ? ? ? }

? ? ? ? });

? ? }

}

4 HandlerThread(Handler+Thread)

HandlerThread內(nèi)部維護了一個消息隊列,避免多次創(chuàng)建和銷毀子線程來進行操作。


HandlerThread本質(zhì)上是一個線程類,它繼承了Thread;

HandlerThread有自己的內(nèi)部Looper對象,可以進行l(wèi)ooper循環(huán);

通過獲取HandlerThread的looper對象傳遞給Handler對象,可以在handleMessage方法中執(zhí)行異步任務。

創(chuàng)建HandlerThread后必須先調(diào)用HandlerThread.start()方法,Thread會先調(diào)用run方法,創(chuàng)建Looper對象。

4.1 實例

// 步驟1:創(chuàng)建HandlerThread實例對象

// 傳入?yún)?shù) = 線程名字,作用 = 標記該線程

HandlerThread mHandlerThread = new HandlerThread("handlerThread");


// 步驟2:啟動線程

mHandlerThread.start();


// 步驟3:創(chuàng)建工作線程Handler & 復寫handleMessage()

// 作用:關聯(lián)HandlerThread的Looper對象、實現(xiàn)消息處理操作 & 與其他線程進行通信

// 注:消息處理操作(HandlerMessage())的執(zhí)行線程 = mHandlerThread所創(chuàng)建的工作線程中執(zhí)行

Handler workHandler = new Handler( handlerThread.getLooper() ) {

@Override

public boolean handleMessage(Message msg) {

? ? ...//消息處理。因為這里是線程中的Looper,是可以做異步延遲的

? ? try {

? ? ? ? ? ? ?//延時操作

? ? ? ? ? ? ?Thread.sleep(1000);

? ? ? ? ?} catch (InterruptedException e) {

? ? ? ? ? ? ?e.printStackTrace();

? ? ? ? ?}

? ? ? ? ?// 這里是不能直接更新UI的,如果想要更新UI必須獲取到主線程的Handler,才能更新

? ? return true;

}

});


// 步驟4:使用工作線程Handler向工作線程的消息隊列發(fā)送消息

// 在工作線程中,當消息循環(huán)時取出對應消息 & 在工作線程執(zhí)行相關操作

? // a. 定義要發(fā)送的消息

? Message msg = Message.obtain();

? msg.what = 2; //消息的標識

? msg.obj = "B"; // 消息的存放

? // b. 通過Handler發(fā)送消息到其綁定的消息隊列

? workHandler.sendMessage(msg);


// 步驟5:結(jié)束線程,即停止線程的消息循環(huán)

? mHandlerThread.quit();

4.2 注意點

內(nèi)存泄漏

關于Handler的內(nèi)存泄露上面提到,使用HandlerThread要注意這塊


In Android, Handler classes should be static or leaks might occur.

1

連續(xù)發(fā)送消息

使用HandlerThread時只是開了一個工作線程,當你執(zhí)行sendMessage n下后,只是將n個消息發(fā)送到消息隊列MessageQueue里排隊,等候派發(fā)消息給Handler再進行對應的操作。


5 IntentService

1、線程任務需按順序在后臺執(zhí)行,比如離線下載

2、不符合多個數(shù)據(jù)同時請求的場景:所有的任務都在同一個Thread looper里執(zhí)行


IntentService是Service的子類,根據(jù)需要處理異步請求(以intent表示)??蛻舳送ㄟ^調(diào)用startService(Intent) 發(fā)送請求,該Service根據(jù)需要啟動,使用工作線程處理依次每個Intent,并在停止工作時停止自身。

它擁有較高的優(yōu)先級,不易被系統(tǒng)殺死(繼承自Service的緣故),因此比較適合執(zhí)行一些高優(yōu)先級的異步任務;

它內(nèi)部通過HandlerThread和Handler實現(xiàn)異步操作

創(chuàng)建IntentService時,只需實現(xiàn)onHandleIntent和構造方法,onHandleIntent為異步方法,可以執(zhí)行耗時操作;

5.1 使用步驟

步驟1:定義 IntentService的子類,需復寫onHandleIntent()方法

public class myIntentService extends IntentService {


? /**?

? ? * 在構造函數(shù)中傳入線程名字

? ? **/??

? ? public myIntentService() {

? ? ? ? // 調(diào)用父類的構造函數(shù)

? ? ? ? // 參數(shù) = 工作線程的名字

? ? ? ? super("myIntentService");

? ? }


? ?/**?

? ? ?* 復寫onHandleIntent()方法

? ? ?* 根據(jù) Intent實現(xiàn) 耗時任務 操作

? ? ?**/??

? ? @Override

? ? protected void onHandleIntent(Intent intent) {


? ? ? ? // 根據(jù) Intent的不同,進行不同的事務處理

? ? ? ? String taskName = intent.getExtras().getString("taskName");

? ? ? ? switch (taskName) {

? ? ? ? ? ? case "task1":

? ? ? ? ? ? ? ? Log.i("myIntentService", "do task1");

? ? ? ? ? ? ? ? break;

? ? ? ? ? ? case "task2":

? ? ? ? ? ? ? ? Log.i("myIntentService", "do task2");

? ? ? ? ? ? ? ? break;

? ? ? ? ? ? default:

? ? ? ? ? ? ? ? break;

? ? ? ? }

? ? }


? ? @Override

? ? public void onCreate() {

? ? ? ? Log.i("myIntentService", "onCreate");

? ? ? ? super.onCreate();

? ? }

? ?/**?

? ? ?* 復寫onStartCommand()方法

? ? ?* 默認實現(xiàn) = 將請求的Intent添加到工作隊列里

? ? ?**/??

? ? @Override

? ? public int onStartCommand(Intent intent, int flags, int startId) {

? ? ? ? Log.i("myIntentService", "onStartCommand");

? ? ? ? return super.onStartCommand(intent, flags, startId);

? ? }


? ? @Override

? ? public void onDestroy() {

? ? ? ? Log.i("myIntentService", "onDestroy");

? ? ? ? super.onDestroy();

? ? }

}


步驟2:在Manifest.xml中注冊服務

<service android:name=".myIntentService">

? ? <intent-filter >

? ? ? ? <action android:name="cn.scu.finch"/>

? ? </intent-filter>

</service>

步驟3:在Activity中開啟Service服務

public class MainActivity extends AppCompatActivity {


? ? @Override

? ? protected void onCreate(Bundle savedInstanceState) {

? ? ? ? super.onCreate(savedInstanceState);

? ? ? ? setContentView(R.layout.activity_main);


? ? ? ? ? ? // 同一服務只會開啟1個工作線程

? ? ? ? ? ? // 在onHandleIntent()函數(shù)里,依次處理傳入的Intent請求

? ? ? ? ? ? // 將請求通過Bundle對象傳入到Intent,再傳入到服務里


? ? ? ? ? ? // 請求1

? ? ? ? ? ? Intent i = new Intent("cn.scu.finch");

? ? ? ? ? ? Bundle bundle = new Bundle();

? ? ? ? ? ? bundle.putString("taskName", "task1");

? ? ? ? ? ? i.putExtras(bundle);

? ? ? ? ? ? startService(i);


? ? ? ? ? ? // 請求2

? ? ? ? ? ? Intent i2 = new Intent("cn.scu.finch");

? ? ? ? ? ? Bundle bundle2 = new Bundle();

? ? ? ? ? ? bundle2.putString("taskName", "task2");

? ? ? ? ? ? i2.putExtras(bundle2);

? ? ? ? ? ? startService(i2);


? ? ? ? ? ? startService(i);? //多次啟動

? ? ? ? }

? ? }

6 ThreadPool(線程池)

1、重用存在的線程,減少對象創(chuàng)建、消亡的開銷,性能佳

2、可有效控制最大并發(fā)線程數(shù),提高系統(tǒng)資源的使用率,同時避免過多資源競爭,


6.1 ThreadPoolExecutor構造參數(shù)解釋

Executor threadPool = new ThreadPoolExecutor(

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? CORE_POOL_SIZE,

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? MAXIMUM_POOL_SIZE,

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? KEEP_ALIVE,

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? TimeUnit.SECONDS,

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? sPoolWorkQueue,

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? sThreadFactory

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? );


corePoolSize: 線程池的核心線程數(shù),默認情況下,核心線程數(shù)會一直在線程池中存活,即使它們處理閑置狀態(tài)。如果將ThreadPoolExecutor的allowCoreThreadTimeOut屬性設置為true,那么閑置的核心線程在等待新任務到來時會執(zhí)行超時策略,這個時間間隔由keepAliveTime所指定,當?shù)却龝r間超過keepAliveTime所指定的時長后,核心線程就會被終止。

maximumPoolSize: 線程池所能容納的最大線程數(shù)量,當活動線程數(shù)到達這個數(shù)值后,后續(xù)的新任務將會被阻塞。如果這個無限大永遠不會阻塞,除非開辟的線程超過了CPU承受的最大范圍。

keepAliveTime: 非核心線程的超時時長,當系統(tǒng)中非核心線程閑置時間超過keepAliveTime之后,則會被回收。如果ThreadPoolExecutor的allowCoreThreadTimeOut屬性設置為true,則該參數(shù)也表示核心線程的超時時長。

unit: keepAliveTime這個參數(shù)的單位,有納秒、微秒、毫秒、秒、分、時、天等。常用的有TimeUnit.MILLISECONDS(毫秒),TimeUnit.SECONDS(秒)以及TimeUnit.MINUTES(分鐘)等。

workQueue: 線程池中的任務隊列,該隊列主要用來存儲已經(jīng)被提交但是尚未執(zhí)行的任務。存儲在這里的任務是由ThreadPoolExecutor的execute方法提交來的。

threadFactory: 為線程池提供創(chuàng)建新線程的功能,這個我們一般使用默認即可。

handler: 拒絕策略,當線程無法執(zhí)行新任務時(一般是由于線程池中的線程數(shù)量已經(jīng)達到最大數(shù)或者線程池關閉導致的),默認情況下,當線程池無法處理新線程時,會拋出一個RejectedExecutionException。

6.2 實例

// 1. 創(chuàng)建線程池

// 創(chuàng)建時,通過配置線程池的參數(shù),從而實現(xiàn)自己所需的線程池

?Executor threadPool = new ThreadPoolExecutor(...);

? // 注:在Java中,已內(nèi)置4種常見線程池,下面會詳細說明

??

// 2. 向線程池提交任務:execute()

// 說明:傳入 Runnable對象

? ?threadPool.execute(new Runnable() {

? ? ? ? @Override

? ? ? ? public void run() {

? ? ? ? ? ? ... // 線程執(zhí)行任務

? ? ? ? }

? ? });


// 3. 關閉線程池shutdown()?

threadPool.shutdown();


// 關閉線程的原理

// a. 遍歷線程池中的所有工作線程

// b. 逐個調(diào)用線程的interrupt()中斷線程(注:無法響應中斷的任務可能永遠無法終止)


// 也可調(diào)用shutdownNow()關閉線程:threadPool.shutdownNow()

// 二者區(qū)別:

// shutdown:設置 線程池的狀態(tài) 為 SHUTDOWN,然后中斷所有沒有正在執(zhí)行任務的線程

// shutdownNow:設置 線程池的狀態(tài) 為 STOP,然后嘗試停止所有的正在執(zhí)行或暫停任務的線程,并返回等待執(zhí)行任務的列表

// 使用建議:一般調(diào)用shutdown()關閉線程池;若任務不一定要執(zhí)行完,則調(diào)用shutdownNow()


6.2 四種線程池

6.2.1 定長線程池(FixedThreadPool)

特點:只有核心線程而且不會被回收、線程數(shù)量固定、任務隊列無大小限制(超出的線程任務會在隊列中等待阻塞)

應用場景:控制線程最大并發(fā)數(shù)


// 1. 創(chuàng)建定長線程池對象 & 設置線程池線程數(shù)量固定為3

ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);


// 2. 創(chuàng)建好Runnable類線程對象 & 需執(zhí)行的任務

Runnable task =new Runnable(){

public void run(){

System.out.println("執(zhí)行任務啦");

}

};

? ? ? ??

// 3. 向線程池提交任務:execute()

fixedThreadPool.execute(task);

? ? ? ??

// 4. 關閉線程池

fixedThreadPool.shutdown();


6.2.2 定時線程池(ScheduledThreadPool )

核心線程數(shù)量固定、非核心線程數(shù)量無限制,不會阻塞(閑置時馬上回收)

應用場景:執(zhí)行定時以及周期性任務


為什么不用Timer做定時和延時任務?


Timer的特點

1.Timer是單線程模式;

2.如果在執(zhí)行任務期間某個TimerTask耗時較久,那么就會影響其它任務的調(diào)度;

3.Timer的任務調(diào)度是基于絕對時間的,對系統(tǒng)時間敏感;

4.Timer不會捕獲執(zhí)行TimerTask時所拋出的異常,由于Timer是單線程,所以一旦出現(xiàn)異常,則線程就會終止,其他任務也得不到執(zhí)行。

ScheduledThreadPoolExecutor的特點

1.ScheduledThreadPoolExecutor是多線程

2.多線程,單個線程耗時操作不會影響影響其它任務的調(diào)度

3.基于相對時間,對系統(tǒng)時間不敏感

4.多線程,單個任務的執(zhí)行異常不會影響其他線程

// 1. 創(chuàng)建 定時線程池對象 & 設置線程池線程數(shù)量固定為5

ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);


// 2. 創(chuàng)建好Runnable類線程對象 & 需執(zhí)行的任務

Runnable task =new Runnable(){

? ?public void run(){

? ? ? ? ? System.out.println("執(zhí)行任務啦");

? ? ? }

};

// 3. 向線程池提交任務:schedule()

scheduledThreadPool.schedule(task, 1, TimeUnit.SECONDS); // 延遲1s后執(zhí)行任務

scheduledThreadPool.scheduleAtFixedRate(task,10,1000,TimeUnit.MILLISECONDS);// 延遲10ms后、每隔1000ms執(zhí)行任務


// 4. 關閉線程池

scheduledThreadPool.shutdown();

6.2.3 可緩存線程池(CachedThreadPool)

特點:只有非核心線程、線程數(shù)量不固定(可無限大)、靈活回收空閑線程(具備超時機制,全部回收時幾乎不占系統(tǒng)資源)、新建線程(無線程可用時),不會阻塞

應用場景:執(zhí)行大量、耗時少的線程任務


// 1. 創(chuàng)建可緩存線程池對象

ExecutorService cachedThreadPool = Executors.newCachedThreadPool();


// 2. 創(chuàng)建好Runnable類線程對象 & 需執(zhí)行的任務

Runnable task =new Runnable(){

public void run(){

? ? System.out.println("執(zhí)行任務啦");

? ? ? ? }

};


// 3. 向線程池提交任務:execute()

cachedThreadPool.execute(task);


// 4. 關閉線程池

cachedThreadPool.shutdown();


//當執(zhí)行第二個任務時第一個任務已經(jīng)完成

//那么會復用執(zhí)行第一個任務的線程,而不用每次新建線程。


6.2.4 單線程化線程池(SingleThreadExecutor)

特點:只有一個核心線程(超過一個任務會阻塞,保證所有任務按照指定順序在一個線程中執(zhí)行,不需要處理線程同步的問題)

應用場景:不適合并發(fā)但可能引起IO阻塞性及影響UI線程響應的操作,如數(shù)據(jù)庫操作,文件操作等


// 1. 創(chuàng)建單線程化線程池

ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();


// 2. 創(chuàng)建好Runnable類線程對象 & 需執(zhí)行的任務

Runnable task =new Runnable(){

? public void run(){

? ? ? ? System.out.println("執(zhí)行任務啦");

? ? ? ? ? ? }

?};


// 3. 向線程池提交任務:execute()

singleThreadExecutor.execute(task);


// 4. 關閉線程池

singleThreadExecutor.shutdown();


6.3 線程池的注意點

雖然線程池是構建多線程應用程序的強大機制,但使用它并不是沒有風險的。用線程池構建的應用程序容易遭受任何其它多線程應用程序容易遭受的所有并發(fā)風險,諸如同步錯誤和死鎖,它還容易遭受特定于線程池的少數(shù)其它風險,諸如與池有關的死鎖、資源不足和線程泄漏。

1.死鎖

任何多線程應用程序都有死鎖風險。當一組進程或線程中的每一個都在等待一個只有該組中另一個進程才能引起的事件時,我們就說這組進程或線程 死鎖了。死鎖的最簡單情形是:線程 A 持有對象 X 的獨占鎖,并且在等待對象 Y 的鎖,而線程 B 持有對象 Y 的獨占鎖,卻在等待對象 X 的鎖。除非有某種方法來打破對鎖的等待(Java 鎖定不支持這種方法),否則死鎖的線程將永遠等下去。

雖然任何多線程程序中都有死鎖的風險,但線程池卻引入了另一種死鎖可能,在那種情況下,所有池線程都在執(zhí)行已阻塞的等待隊列中另一任務的執(zhí)行結(jié)果的任務,但這一任務卻因為沒有未被占用的線程而不能運行。當線程池被用來實現(xiàn)涉及許多交互對象的模擬,被模擬的對象可以相互發(fā)送查詢,這些查詢接下來作為排隊的任務執(zhí)行,查詢對象又同步等待著響應時,會發(fā)生這種情況。

2.資源不足

線程池的一個優(yōu)點在于:相對于其它替代調(diào)度機制而言,它們通常執(zhí)行得很好。但只有恰當?shù)卣{(diào)整了線程池大小時才是這樣的。線程消耗包括內(nèi)存和其它系統(tǒng)資源在內(nèi)的大量資源。除了 Thread 對象所需的內(nèi)存之外,每個線程都需要兩個可能很大的執(zhí)行調(diào)用堆棧。除此以外,JVM 可能會為每個 Java 線程創(chuàng)建一個本機線程,這些本機線程將消耗額外的系統(tǒng)資源。最后,雖然線程之間切換的調(diào)度開銷很小,但如果有很多線程,環(huán)境切換也可能嚴重地影響程序的性能。

如果線程池太大,那么被那些線程消耗的資源可能嚴重地影響系統(tǒng)性能。在線程之間進行切換將會浪費時間,而且使用超出比您實際需要的線程可能會引起資源匱乏問題,因為池線程正在消耗一些資源,而這些資源可能會被其它任務更有效地利用。除了線程自身所使用的資源以外,服務請求時所做的工作可能需要其它資源,例如 JDBC 連接、套接字或文件。這些也都是有限資源,有太多的并發(fā)請求也可能引起失效,例如不能分配 JDBC 連接。

3.并發(fā)錯誤

線程池和其它排隊機制依靠使用 wait() 和 notify() 方法,這兩個方法都難于使用。如果編碼不正確,那么可能丟失通知,導致線程保持空閑狀態(tài),盡管隊列中有工作要處理。使用這些方法時,必須格外小心;即便是專家也可能在它們上面出錯。而最好使用現(xiàn)有的、已經(jīng)知道能工作的實現(xiàn),例如 util.concurrent 包。

4.線程泄漏

各種類型的線程池中一個嚴重的風險是線程泄漏,當從池中除去一個線程以執(zhí)行一項任務,而在任務完成后該線程卻沒有返回池時,會發(fā)生這種情況。發(fā)生線程泄漏的一種情形出現(xiàn)在任務拋出一個 RuntimeException 或一個 Error 時。如果池類沒有捕捉到它們,那么線程只會退出而線程池的大小將會永久減少一個。當這種情況發(fā)生的次數(shù)足夠多時,線程池最終就為空,而且系統(tǒng)將停止,因為沒有可用的線程來處理任務。

有些任務可能會永遠等待某些資源或來自用戶的輸入,而這些資源又不能保證變得可用,用戶可能也已經(jīng)回家了,諸如此類的任務會永久停止,而這些停止的任務也會引起和線程泄漏同樣的問題。如果某個線程被這樣一個任務永久地消耗著,那么它實際上就被從池除去了。對于這樣的任務,應該要么只給予它們自己的線程,要么只讓它們等待有限的時間。

5.請求過載

僅僅是請求就壓垮了服務器,這種情況是可能的。在這種情形下,我們可能不想將每個到來的請求都排隊到我們的工作隊列,因為排在隊列中等待執(zhí)行的任務可能會消耗太多的系統(tǒng)資源并引起資源缺乏。在這種情形下決定如何做取決于您自己;在某些情況下,您可以簡單地拋棄請求,依靠更高級別的協(xié)議稍后重試請求,您也可以用一個指出服務器暫時很忙的響應來拒絕請求。



Android多線程開發(fā)總結(jié)的評論 (共 條)

分享到微博請遵守國家法律
侯马市| 神池县| 广丰县| 福海县| 剑阁县| 石柱| 枝江市| 玛曲县| 新化县| 长沙县| 和龙市| 田东县| 泰兴市| 玉溪市| 肥西县| 肥东县| 南江县| 昌吉市| 襄樊市| 溧阳市| 武穴市| 雷波县| 马公市| 故城县| 泰州市| 读书| 平顺县| 陵川县| 弥勒县| 顺昌县| 洮南市| 宁晋县| 叶城县| 富阳市| 玛多县| 仙居县| 射洪县| 醴陵市| 迁西县| 建德市| 鄂托克旗|