在程序開(kāi)發(fā)的實(shí)踐當(dāng)中,為了讓程序表現(xiàn)得更加流暢,我們肯定會(huì)需要使用到多線程來(lái)提升程序的并發(fā)執(zhí)行性能。但是編寫(xiě)多線程并發(fā)的代碼一直以來(lái)都是一個(gè)相對(duì)棘手的問(wèn)題,所以想要獲得更佳的程序性能,我們非常有必要掌握多線程并發(fā)編程的基礎(chǔ)技能。
引入多線程而可能伴隨而來(lái)的內(nèi)存問(wèn)題
雖然使用多線程可以提高程序的并發(fā)量,但是我們需要特別注意因?yàn)橐攵嗑€程而可能伴隨而來(lái)的內(nèi)存問(wèn)題。例如:在 Activity 內(nèi)部定義的一個(gè) AsyncTask,它屬于一個(gè)內(nèi)部類(lèi),該類(lèi)本身和外面的 Activity 是有引用關(guān)系的,如果 Activity 要銷(xiāo)毀的時(shí)候,AsyncTask 還仍然在運(yùn)行,這會(huì)導(dǎo)致 Activity 沒(méi)有辦法完全釋放,從而引發(fā)內(nèi)存泄漏。所以說(shuō),多線程是提升程序性能的有效手段之一,但是使用多線程卻需要十分謹(jǐn)慎小心,如果不了解背后的執(zhí)行機(jī)制以及使用的注意事項(xiàng),很可能引起嚴(yán)重的問(wèn)題。
下面我們來(lái)一個(gè)一個(gè)分析下系統(tǒng)為我們提供的幾種多線程方式,包括:AsyncTask,HandlerThread,IntentService,ThreadPool
1. AsyncTask
使用場(chǎng)景:
為 UI 線程與工作線程之間進(jìn)行快速的切換提供一種簡(jiǎn)單便捷的機(jī)制。適用于當(dāng)下立即需要啟動(dòng),但是異步執(zhí)行的生命周期短暫的使用場(chǎng)景。
基本使用
AsyncTask是一個(gè)抽象方法,如果想使用它,需要先創(chuàng)建一個(gè)子類(lèi)來(lái)繼承他,還需要為其指定三個(gè)泛型參數(shù):
- Params
在執(zhí)行AsyncTask時(shí)需要傳入的參數(shù),可用于在后臺(tái)任務(wù)中使用。 - Progress
后臺(tái)任務(wù)執(zhí)行時(shí),如果需要在界面上顯示當(dāng)前的進(jìn)度,則使用這里指定的泛型作為進(jìn)度單位。 - Result
當(dāng)任務(wù)執(zhí)行完畢后,如果需要對(duì)結(jié)果進(jìn)行返回,則使用這里指定的泛型作為返回值類(lèi)型。
經(jīng)常需要去重寫(xiě)的方法有以下四個(gè):
- onPreExecute()
這個(gè)方法會(huì)在后臺(tái)任務(wù)開(kāi)始執(zhí)行之間調(diào)用,用于進(jìn)行一些界面上的初始化操作,比如顯示一個(gè)進(jìn)度條對(duì)話框等。 - doInBackground(Params...)
這個(gè)方法中的所有代碼都會(huì)在子線程中運(yùn)行,我們應(yīng)該在這里去處理所有的耗時(shí)任務(wù)。任務(wù)一旦完成就可以通過(guò)return語(yǔ)句來(lái)將任務(wù)的執(zhí)行結(jié)果進(jìn)行返回,如果AsyncTask的第三個(gè)泛型參數(shù)指定的是Void,就可以不返回任務(wù)執(zhí)行結(jié)果。注意,在這個(gè)方法中是不可以進(jìn)行UI操作的,如果需要更新UI元素,比如說(shuō)反饋當(dāng)前任務(wù)的執(zhí)行進(jìn)度,可以調(diào)用publishProgress(Progress...)方法來(lái)完成。 - onProgressUpdate(Progress...)
當(dāng)在后臺(tái)任務(wù)中調(diào)用了publishProgress(Progress...)方法后,這個(gè)方法就很快會(huì)被調(diào)用,方法中攜帶的參數(shù)就是在后臺(tái)任務(wù)中傳遞過(guò)來(lái)的。在這個(gè)方法中可以對(duì)UI進(jìn)行操作,利用參數(shù)中的數(shù)值就可以對(duì)界面元素進(jìn)行相應(yīng)的更新。 - onPostExecute(Result)
當(dāng)后臺(tái)任務(wù)執(zhí)行完畢并通過(guò)return語(yǔ)句進(jìn)行返回時(shí),這個(gè)方法就很快會(huì)被調(diào)用。返回的數(shù)據(jù)會(huì)作為參數(shù)傳遞到此方法中,可以利用返回的數(shù)據(jù)來(lái)進(jìn)行一些UI操作,比如說(shuō)提醒任務(wù)執(zhí)行的結(jié)果,以及關(guān)閉掉進(jìn)度條對(duì)話框等。
最后在需要的地方調(diào)用方法 new MyAsyncTask().execute(); 就可以運(yùn)行了。
注意事項(xiàng):
首先,默認(rèn)情況下,所有的 AsyncTask 任務(wù)都是被線性調(diào)度執(zhí)行的,他們處在同一個(gè)任務(wù)隊(duì)列當(dāng)中,按順序逐個(gè)執(zhí)行。假設(shè)你按照順序啟動(dòng)20個(gè) AsyncTask,一旦其中的某個(gè) AsyncTask 執(zhí)行時(shí)間過(guò)長(zhǎng),隊(duì)列中的其他剩余 AsyncTask 都處于阻塞狀態(tài),必須等到該任務(wù)執(zhí)行完畢之后才能夠有機(jī)會(huì)執(zhí)行下一個(gè)任務(wù)。為了解決上面提到的線性隊(duì)列等待的問(wèn)題,我們可以使用 AsyncTask.executeOnExecutor()強(qiáng)制指定 AsyncTask 使用線程池并發(fā)調(diào)度任務(wù)。
其次,如何才能夠真正的取消一個(gè) AsyncTask 的執(zhí)行呢?我們知道 AsyncTaks 有提供 cancel()的方法,但是這個(gè)方法實(shí)際上做了什么事情呢?線程本身并不具備中止正在執(zhí)行的代碼的能力,為了能夠讓一個(gè)線程更早的被銷(xiāo)毀,我們需要在 doInBackground()的代碼中不斷的添加程序是否被中止的判斷邏輯。一旦任務(wù)被成功中止,AsyncTask 就不會(huì)繼續(xù)調(diào)用 onPostExecute(),而是通過(guò)調(diào)用 onCancelled()的回調(diào)方法反饋任務(wù)執(zhí)行取消的結(jié)果。我們可以根據(jù)任務(wù)回調(diào)到哪個(gè)方法(是 onPostExecute 還是 onCancelled)來(lái)決定是對(duì) UI 進(jìn)行正常的更新還是把對(duì)應(yīng)的任務(wù)所占用的內(nèi)存進(jìn)行銷(xiāo)毀等。
最后,使用 AsyncTask 很容易導(dǎo)致內(nèi)存泄漏,一旦把 AsyncTask 寫(xiě)成 Activity 的內(nèi)部類(lèi)的形式就很容易因?yàn)?AsyncTask 生命周期的不確定而導(dǎo)致 Activity 發(fā)生泄漏。
綜上所述,AsyncTask 雖然提供了一種簡(jiǎn)單便捷的異步機(jī)制,但是我們還是很有必要特別關(guān)注到他的缺點(diǎn),避免出現(xiàn)因?yàn)槭褂缅e(cuò)誤而導(dǎo)致的嚴(yán)重系統(tǒng)性能問(wèn)題。
2. HandlerThread
使用場(chǎng)景:
為某些回調(diào)方法或者等待某些任務(wù)的執(zhí)行設(shè)置一個(gè)專(zhuān)屬的線程,并提供線程任務(wù)的調(diào)度機(jī)制。
大多數(shù)情況下,AsyncTask 都能夠滿足多線程并發(fā)的場(chǎng)景需要(在工作線程執(zhí)行任務(wù)并返回結(jié)果到主線程),但是它并不是萬(wàn)能的。例如打開(kāi)相機(jī)之后的預(yù)覽幀數(shù)據(jù)是通過(guò) onPreviewFrame()的方法進(jìn)行回調(diào)的,onPreviewFrame()和 open()相機(jī)的方法是執(zhí)行在同一個(gè)線程的。如果使用 AsyncTask,會(huì)因?yàn)?AsyncTask 默認(rèn)的線性執(zhí)行的特性(即使換成并發(fā)執(zhí)行)會(huì)導(dǎo)致因?yàn)闊o(wú)法把任務(wù)及時(shí)傳遞給工作線程而導(dǎo)致任務(wù)在主線程中被延遲,直到工作線程空閑,才可以把任務(wù)切換到工作線程中進(jìn)行執(zhí)行。所以我們需要的是一個(gè)執(zhí)行在工作線程,同時(shí)又能夠處理隊(duì)列中的復(fù)雜任務(wù)的功能,而 HandlerThread 的出現(xiàn)就是為了實(shí)現(xiàn)這個(gè)功能的,它組合了 Handler,MessageQueue,Looper 實(shí)現(xiàn)了一個(gè)長(zhǎng)時(shí)間運(yùn)行的線程,不斷的從隊(duì)列中獲取任務(wù)進(jìn)行執(zhí)行的功能。
基本用法:
HandlerThread 繼承于 Thread,它本質(zhì)上是一個(gè)線程,只不過(guò)是 Android 為我們封裝好了 Looper 和 MessageQueue的線程,簡(jiǎn)化了操作。使用方法很簡(jiǎn)單:
// 創(chuàng)建一個(gè)線程,線程名字 : handlerThreadTest
mHandlerThread = new HandlerThread("handlerThreadTest");
mHandlerThread.start();
// Handler 接收消息
final Handler mHandler = new Handler(mHandlerThread.getLooper()) {
@Override
public void handleMessage(Message msg) {
Log.e("Test", "收到 " + msg.obj.toString() + " 在 "
+ Thread.currentThread().getName());
}
};
mTextView = (TextView) findViewById(R.id.text_view);
// 主線程發(fā)出消息
mTextView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Message msg = new Message();
msg.obj = "第一條信息";
mHandler.sendMessage(msg);
Log.e("Test", "發(fā)出 " + msg.obj.toString() + " 在 "
+ Thread.currentThread().getName());
}
});
// 子線程發(fā)出消息
new Thread(new Runnable() {
@Override
public void run() {
Message msg = new Message();
msg.obj = "第二條信息";
mHandler.sendMessage(msg);
Log.e("Test", "發(fā)出 " + msg.obj.toString() + " 在 "
+ Thread.currentThread().getName());
}
}).start();
最后在不需要的時(shí)候記得調(diào)用quit();
@Override
protected void onDestroy() {
super.onDestroy();
//停止消息循環(huán)
mHandlerThread.quit();
}
注意事項(xiàng):
HandlerThread 比較合適處理那些在工作線程執(zhí)行,需要花費(fèi)時(shí)間偏長(zhǎng)的任務(wù)。我們只需要把任務(wù)發(fā)送給 HandlerThread,然后就只需要等待任務(wù)執(zhí)行結(jié)束的時(shí)候通知返回到主線程就好了。
另外很重要的一點(diǎn)是,一旦我們使用了 HandlerThread,需要特別注意給 HandlerThread 設(shè)置不同的線程優(yōu)先級(jí),CPU 會(huì)根據(jù)設(shè)置的不同線程優(yōu)先級(jí)對(duì)所有的線程進(jìn)行調(diào)度優(yōu)化。
3. IntentSerice
默認(rèn)的 Service 是執(zhí)行在主線程的,可是通常情況下,這很容易影響到程序的繪制性能(搶占了主線程的資源)。除了前面介紹過(guò)的 AsyncTask 與 HandlerThread,我們還可以選擇使用 IntentService 來(lái)實(shí)現(xiàn)異步操作。IntentService 繼承自普通 Service 同時(shí)又在內(nèi)部創(chuàng)建了一個(gè) HandlerThread,在 onHandlerIntent()的回調(diào)里面處理扔到 IntentService 的任務(wù),在執(zhí)行完任務(wù)后會(huì)自動(dòng)停止。所以 IntentService 就不僅僅具備了異步線程的特性,還同時(shí)保留了 Service 不受主頁(yè)面生命周期影響,優(yōu)先級(jí)比較高,適合執(zhí)行高優(yōu)先級(jí)的后臺(tái)任務(wù),不容易被殺死的特點(diǎn)。
使用場(chǎng)景:
適合于執(zhí)行由 UI 觸發(fā)的后臺(tái) Service 任務(wù),并可以把后臺(tái)任務(wù)執(zhí)行的情況通過(guò)一定的機(jī)制反饋給 UI。
基本用法:
public class MyIntentService extends IntentService {
public static UpdateUI updateUI;
/**
* Creates an IntentService. Invoked by your subclass's constructor.
*
* @param name Used to name the worker thread, important only for debugging.
*/
public MyIntentService(String name) {
super(name);
}
public interface UpdateUI{
void updateUI(Message message);
}
public static void setUpdateUI(UpdateUI updateUIInterface){
updateUI = updateUIInterface;
}
@Override
protected void onHandleIntent(@Nullable Intent intent) {
// 執(zhí)行耗時(shí)操作
Message msg1 = new Message();
msg1.obj ="我是耗時(shí)操作";
// 調(diào)用回調(diào) (也可以通過(guò)廣播機(jī)制完成)
if(updateUI != null){
updateUI.updateUI(msg1);
}
}
}
最后 Activity 通過(guò) Handler 獲取數(shù)據(jù)并刷新UI。
注意事項(xiàng):
使用 IntentService 需要特別留意以下幾點(diǎn):
首先,因?yàn)?IntentService 內(nèi)置的是 HandlerThread 作為異步線程,所以每一個(gè)交給 IntentService 的任務(wù)都將以隊(duì)列的方式逐個(gè)被執(zhí)行到,一旦隊(duì)列中有某個(gè)任務(wù)執(zhí)行時(shí)間過(guò)長(zhǎng),那么就會(huì)導(dǎo)致后續(xù)的任務(wù)都會(huì)被延遲處理。
其次,通常使用到 IntentService 的時(shí)候,我們會(huì)結(jié)合使用 BroadcastReceiver 把工作線程的任務(wù)執(zhí)行結(jié)果返回給主 UI 線程。使用廣播容易引起性能問(wèn)題,我們可以使用 LocalBroadcastManager 來(lái)發(fā)送只在程序內(nèi)部傳遞的廣播,從而提升廣播的性能。我們也可以使用 runOnUiThread() 快速回調(diào)到主 UI 線程。
最后,包含正在運(yùn)行的 IntentService 的程序相比起純粹的后臺(tái)程序更不容易被系統(tǒng)殺死,該程序的優(yōu)先級(jí)是介于前臺(tái)程序與純后臺(tái)程序之間的。
4. ThreadPool
系統(tǒng)為我們提供了 ThreadPoolExecutor 來(lái)實(shí)現(xiàn)多線程并發(fā)執(zhí)行任務(wù)。
使用場(chǎng)景:
把任務(wù)分解成不同的單元,分發(fā)到各個(gè)不同的線程上,進(jìn)行同時(shí)并發(fā)處理。
基本用法:
線程池有四個(gè)構(gòu)造方法,這四個(gè)構(gòu)造方法咋一看,前面三個(gè)都是調(diào)用第四個(gè)構(gòu)造方法實(shí)現(xiàn)的,每一個(gè)構(gòu)造方法都特別復(fù)雜,參數(shù)很多,使用起來(lái)比較麻煩。
下面列出是四種構(gòu)造方法,從簡(jiǎn)單到復(fù)雜:
- 第一種
ThreadPoolExecutor(
int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue
);
- 第二種
ThreadPoolExecutor(
int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
RejectedExecutionHandler handler
);
- 第三種
ThreadPoolExecutor(
int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory
);
- 第四種
ThreadPoolExecutor(
int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler
);
參數(shù)作用:
corePoolSize:
池中所保存的線程數(shù),包括空閑線程。maximumPoolSize:
池中允許的最大線程數(shù)。keepAliveTime:
當(dāng)線程數(shù)大于核心時(shí),此為終止前多余的空閑線程等待新任務(wù)的最長(zhǎng)時(shí)間。unit:
keepAliveTime參數(shù)的時(shí)間單位workQueue:
執(zhí)行前用于保持任務(wù)的隊(duì)列。此隊(duì)列僅保持由 execute 方法提交的 Runnable 任務(wù)。threadFactory
執(zhí)行程序創(chuàng)建新線程時(shí)使用的工廠。**handler **
由于超出線程范圍和隊(duì)列容量而使執(zhí)行被阻塞時(shí)所使用的處理程序。
這里用復(fù)雜的一個(gè)構(gòu)造方法說(shuō)明如何手動(dòng)創(chuàng)建一個(gè)線程池。
package com.example.thread.threadpool;
import java.util.Collection;
import java.util.Iterator;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* @auther MaxLiu
* @time 2017/2/23
*/
public class ThreadPoolTest {
private static final int CORE_POOL_SIZE = 5;
private static final int MAX_POOL_SIZE = 10;
private static final int BLOCK_POOL_SIZE = 2;
private static final int ALIVE_POOL_SIZE = 2;
private static ThreadPoolExecutor executor;
public static void main(String args[]) {
executor = new ThreadPoolExecutor(
CORE_POOL_SIZE,// 核心線程數(shù) 最小
MAX_POOL_SIZE,// 最大執(zhí)行線程數(shù)
ALIVE_POOL_SIZE,// 空閑線程超時(shí)
TimeUnit.SECONDS,// 超時(shí)時(shí)間單位
// 當(dāng)線程池達(dá)到corePoolSize時(shí),新提交任務(wù)將被放入workQueue中,
// 等待線程池中任務(wù)調(diào)度執(zhí)行
new ArrayBlockingQueue<Runnable>(BLOCK_POOL_SIZE),// 阻塞隊(duì)列大小
// 線程工廠,為線程池提供創(chuàng)建新線程的功能,它是一個(gè)接口,
// 只有一個(gè)方法:Thread newThread(Runnable r)
Executors.defaultThreadFactory(),
// 線程池對(duì)拒絕任務(wù)的處理策略。一般是隊(duì)列已滿或者無(wú)法成功執(zhí)行任務(wù),
// 這時(shí)ThreadPoolExecutor會(huì)調(diào)用handler的rejectedExecution
// 方法來(lái)通知調(diào)用者
new ThreadPoolExecutor.AbortPolicy()
);
executor.allowCoreThreadTimeOut(true);
/*
* ThreadPoolExecutor默認(rèn)有四個(gè)拒絕策略:
*
* 1、ThreadPoolExecutor.AbortPolicy() 直接拋出異常RejectedExecutionException
* 2、ThreadPoolExecutor.CallerRunsPolicy() 直接調(diào)用run方法并且阻塞執(zhí)行
* 3、ThreadPoolExecutor.DiscardPolicy() 直接丟棄后來(lái)的任務(wù)
* 4、ThreadPoolExecutor.DiscardOldestPolicy() 丟棄在隊(duì)列中隊(duì)首的任務(wù)
*/
for (int i = 0; i < 10; i++) {
try {
executor.execute(new WorkerThread("線程 --> " + i));
LOG();
} catch (Exception e) {
System.out.println("AbortPolicy...");
}
}
executor.shutdown();
// 所有任務(wù)執(zhí)行完畢后再次打印日志
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(1000);
System.out.println("\n\n---------執(zhí)行完畢---------\n");
LOG();
} catch (Exception e) {
e.printStackTrace();
}
}
}).start();
}
/**
* 打印 Log 信息
*/
private static void LOG() {
System.out.println(" ==============線程池===============\n"
+ " 線程池中線程數(shù) : " + executor.getPoolSize()
+ " 等待執(zhí)行線程數(shù) : " + executor.getQueue().size()
+ " 所有的任務(wù)數(shù) : " + executor.getTaskCount()
+ " 執(zhí)行任務(wù)的線程數(shù) : " + executor.getActiveCount()
+ " 執(zhí)行完畢的任務(wù)數(shù) : " + executor.getCompletedTaskCount()
);
}
// 模擬線程任務(wù)
public static class WorkerThread implements Runnable {
private String threadName;
public WorkerThread(String threadName) {
this.threadName = threadName;
}
@Override
public synchronized void run() {
int i = 0;
boolean flag = true;
try {
while (flag) {
i++;
if (i > 2) flag = false;
}
} catch (Exception e) {
e.printStackTrace();
}
}
public String getThreadName() {
return threadName;
}
}
}
結(jié)果:


注意事項(xiàng):
- 使用線程池需要特別注意同時(shí)并發(fā)線程數(shù)量的控制,理論上來(lái)說(shuō),我們可以設(shè)置任意你想要的并發(fā)數(shù)量,但是這樣做非常的不好。因?yàn)?CPU 只能同時(shí)執(zhí)行固定數(shù)量的線程數(shù),一旦同時(shí)并發(fā)的線程數(shù)量超過(guò) CPU 能夠同時(shí)執(zhí)行的閾值,CPU 就需要花費(fèi)精力來(lái)判斷到底哪些線程的優(yōu)先級(jí)比較高,需要在不同的線程之間進(jìn)行調(diào)度切換。一旦同時(shí)并發(fā)的線程數(shù)量達(dá)到一定的量級(jí),這個(gè)時(shí)候 CPU 在不同線程之間進(jìn)行調(diào)度的時(shí)間就可能過(guò)長(zhǎng),反而導(dǎo)致性能?chē)?yán)重下降。
- 另外需要關(guān)注的一點(diǎn)是,每開(kāi)一個(gè)新的線程,都會(huì)耗費(fèi)至少 64K+ 的內(nèi)存。為了能夠方便的對(duì)線程數(shù)量進(jìn)行控制,ThreadPoolExecutor 為我們提供了初始化的并發(fā)線程數(shù)量,以及最大的并發(fā)數(shù)量進(jìn)行設(shè)置。
- 另外需要關(guān)注的一個(gè)問(wèn)題是:Runtime.getRuntime().availableProcesser()方法并不可靠,他返回的值并不是真實(shí)的 CPU 核心數(shù),因?yàn)?CPU 會(huì)在某些情況下選擇對(duì)部分核心進(jìn)行睡眠處理,在這種情況下,返回的數(shù)量就只能是激活的 CPU 核心數(shù)。