概述
線程是操作系統(tǒng)調(diào)度的最小單元版扩,且又是一種有限資源,它的創(chuàng)建和銷毀都會(huì)有相應(yīng)的系統(tǒng)開(kāi)銷侄泽。
若線程數(shù)量大于CPU核心數(shù)量(一般來(lái)說(shuō)礁芦,線程數(shù)量都會(huì)大于CPU核心數(shù)量),系統(tǒng)會(huì)通過(guò)時(shí)間片輪轉(zhuǎn)的方式調(diào)度每一個(gè)線程悼尾。
頻繁的創(chuàng)建和銷毀線程柿扣,所帶來(lái)的系統(tǒng)開(kāi)銷巨大,需要通過(guò)線程池來(lái)避免這個(gè)問(wèn)題诀豁。
Android沿用了JAVA的線程模型窄刘,分為主線程與子線程。
主線程是指進(jìn)程所擁有的線程舷胜,默認(rèn)情況下娩践,一個(gè)進(jìn)程只有一個(gè)線程,即主線程烹骨。主線程用來(lái)運(yùn)行四大組件翻伺,以及處理界面相關(guān)的邏輯。主線程為了保持較高的響應(yīng)速度沮焕,不能執(zhí)行耗時(shí)操作吨岭,否則會(huì)出現(xiàn)界面卡頓。
子線程又叫做工作線程峦树,除了主線程以外的線程都叫做子線程辣辫。子線程用來(lái)處理耗時(shí)任務(wù)旦事,比如網(wǎng)絡(luò)請(qǐng)求,I/O操作等急灭。
Android中線程的形態(tài)
Android中姐浮,可以作為線程的類,除了傳統(tǒng)的Thread以外葬馋,還有AsyncTask卖鲤、IntentService、HandlerThread畴嘶,他們的底層實(shí)現(xiàn)也是線程蛋逾,但他們有特殊的表現(xiàn)形式,使用起來(lái)也各有優(yōu)缺點(diǎn)窗悯。
AsyncTask
AsyncTask是一種輕量的異步類区匣,內(nèi)部封裝了線程池和Handler。在線程池中執(zhí)行后臺(tái)任務(wù)蟀瞧,并把執(zhí)行的進(jìn)度和結(jié)果傳遞給主線程沉颂,主要被用來(lái)在子線程中更新UI。
AsyncTask是一個(gè)抽象泛型類悦污,他的聲明如下
public abstract class AsyncTask<Params, Progress, Result>
其中铸屉,
Params表示參數(shù)類型;
Progress表示后臺(tái)任務(wù)執(zhí)行進(jìn)度的類型切端;
Rusult表示后臺(tái)任務(wù)返回值的類型彻坛。
AsyncTask有幾個(gè)常用的回調(diào)方法,他們的作用分別為:
- onPreExecute()踏枣,在主線程中執(zhí)行昌屉,異步任務(wù)執(zhí)行之前調(diào)用,用來(lái)做些準(zhǔn)備工作茵瀑;
- doInBackground(Params... params)间驮,在線程池中執(zhí)行,用于執(zhí)行異步任務(wù)马昨。并且在此方法中竞帽,可以通過(guò)調(diào)用publishProgress方法來(lái)更新任務(wù)進(jìn)度。此方法還要提供返回值給onPostExecute鸿捧;
- onProgressUpdate(Progress... value)屹篓,在主線程中執(zhí)行,后臺(tái)任務(wù)執(zhí)行進(jìn)度發(fā)生改變時(shí)調(diào)用匙奴。publishProgress方法會(huì)調(diào)用onProgressUpdate方法堆巧,不要直接調(diào)用它;
- onPostExecute(Result result),在主線程中執(zhí)行谍肤,異步任務(wù)之后被調(diào)用啦租。result即為doInBackground提供的返回值。
- onCancelled()荒揣,在主線程中執(zhí)行刷钢,當(dāng)異步任務(wù)被取消的時(shí)候會(huì)被調(diào)用。
這幾個(gè)方法的執(zhí)行順序是onPreExecute->doInBackground->onPostExecute乳附。當(dāng)異步任務(wù)被取消時(shí),onCancelled會(huì)被調(diào)用伴澄,此時(shí)onPostExecute不會(huì)被調(diào)用赋除。
一個(gè)例子:
class Download extends AsyncTask<String, Integer, Integer> {
@Override
protected void onPreExecute() {
// 在主線程中執(zhí)行,異步任務(wù)執(zhí)行之前調(diào)用非凌,用來(lái)做些準(zhǔn)備工作
super.onPreExecute();
}
@Override
protected Integer doInBackground(String... strings) {
// 在線程池中執(zhí)行举农,用于執(zhí)行異步任務(wù)。并且在此方法中敞嗡,可以通過(guò)調(diào)用publishProgress方法來(lái)更新任務(wù)進(jìn)度颁糟。此方法還要提供返回值給onPostExecute
tv_text.setText("TheThread 準(zhǔn)備下載: " + strings[0]);
int i = 0;
for (; i < 100; i++) {
SystemClock.sleep(1000);
publishProgress(i);
}
return i;
}
@Override
protected void onProgressUpdate(Integer... values) {
// 在出現(xiàn)場(chǎng)中執(zhí)行,后臺(tái)任務(wù)執(zhí)行進(jìn)度發(fā)生改變時(shí)調(diào)用喉悴。publishProgress方法會(huì)調(diào)用onProgressUpdate方法棱貌,不要直接調(diào)用它
tv_text.setText("TheThread 正在進(jìn)行: " + values[0]);
super.onProgressUpdate(values);
}
@Override
protected void onPostExecute(Integer integer) {
// 在主線程中執(zhí)行,異步任務(wù)之后被調(diào)用箕肃。result即為doInBackground提供的返回值
tv_text.setText("TheThread 完成: " + integer);
super.onPostExecute(integer);
}
@Override
protected void onCancelled() {
// 在主線程中執(zhí)行婚脱,當(dāng)異步任務(wù)被取消的時(shí)候會(huì)被調(diào)用
super.onCancelled();
}
}
...
// 執(zhí)行
new Download().execute("一個(gè)文件");
下載也是AsyncTask常見(jiàn)用途,下載過(guò)程中可以通過(guò)publishProgress更新進(jìn)度條勺像,當(dāng)下載完成障贸,也就是doInBackground給出返回值時(shí),onPostExecute會(huì)被調(diào)用代表這個(gè)任務(wù)已經(jīng)結(jié)束吟宦。
AsyncTask在使用時(shí)篮洁,也有一些限制條件:
- 一個(gè)AsyncTask只能調(diào)用一次execute;
- AsyncTask執(zhí)行任務(wù)是串行執(zhí)行的殃姓,若想并行執(zhí)行袁波,需要調(diào)用executeOnExecutor方法。
PS:關(guān)于網(wǎng)上一個(gè)討論
《安卓開(kāi)發(fā)藝術(shù)探索》:
AsyncTask的對(duì)象必須在主線程中創(chuàng)建
execute方法必須在UI線程中調(diào)用
書(shū)中講得很清楚辰狡,必須在主線程中首次加載锋叨,是因?yàn)锳syncTask底層用到了Handler,在AsyncTask加載時(shí)會(huì)初始化其內(nèi)部的Handler宛篇。但是在4.1以后娃磺,ActivityThread的main方法會(huì)調(diào)用AsyncTask的init方法,此時(shí)其內(nèi)部的Handler已被初始化叫倍,所以現(xiàn)在在子線程中調(diào)用AsyncTask的創(chuàng)建并execute也沒(méi)問(wèn)題偷卧。
所以書(shū)上的這句話大概是筆誤豺瘤?
// 可以正常執(zhí)行不會(huì)報(bào)錯(cuò)的代碼
new Thread(new Runnable() {
@Override
public void run() {
new Download().execute("一個(gè)文件");
}
}).start();
HandlerThread
HandlerThread是Thread,特殊之處在于它的內(nèi)部听诸,主動(dòng)開(kāi)啟了消息循環(huán)Looper坐求。
結(jié)合Hanlder的內(nèi)容,HandlerThread其實(shí)很好理解晌梨。我們知道如果在Activity中要使用Handler桥嗤,是不需要刻意創(chuàng)建Looper的,因?yàn)锳ctivity會(huì)為我們創(chuàng)建好一個(gè)Looper供我們使用仔蝌,但是在子線程中使用Handler就必須自己創(chuàng)建Looper泛领,否則會(huì)報(bào)錯(cuò)。HandlerThread就是為我們提供了一個(gè)自帶Looper的Thread敛惊,作用主要是為我們提供一個(gè)存在于子線程中的Looper渊鞋。
普通Thread是通過(guò)run方法執(zhí)行一個(gè)任務(wù),HandlerThread需要通過(guò)一個(gè)Handler的消息的方式來(lái)執(zhí)行一個(gè)任務(wù)瞧挤,它的run方法是一個(gè)無(wú)限循環(huán)锡宋,在不使用的時(shí)候要通過(guò)quit方法來(lái)終止其線程的執(zhí)行。
HandlerThread適用于會(huì)長(zhǎng)時(shí)間在后臺(tái)運(yùn)行特恬,間隔觸發(fā)的情況执俩,比如實(shí)時(shí)更新。這就是谷歌爸爸給開(kāi)發(fā)者提供的一個(gè)輪子癌刽,當(dāng)然自己根據(jù)這個(gè)原理實(shí)現(xiàn)的話奠滑,也不差。
HandlerThread的主要應(yīng)用場(chǎng)景是IntentService妒穴。
IntentService
IntentService是一種特殊的Service宋税,它是一個(gè)抽象類,內(nèi)部封裝了HandlerThread和Handler讼油〗苋可以用于執(zhí)行后臺(tái)耗時(shí)的任務(wù),完成后會(huì)自動(dòng)停止矮台。由于他是一個(gè)Service乏屯,所以它的優(yōu)先級(jí)比單純的線程要高很多,不容易被系統(tǒng)殺死瘦赫。
當(dāng)IntentService第一次啟動(dòng)時(shí)辰晕,會(huì)創(chuàng)建一個(gè)HandlerThread,再通過(guò)這個(gè)HandlerThread的Looper來(lái)構(gòu)造一個(gè)Handler對(duì)象mServiceHandler确虱。因?yàn)镠andlerThread的Looper是在子線程中初始化的含友,所以mServiceHandler會(huì)把從主線程(Service線程)中的任務(wù)拿到子線程中執(zhí)行,從而避免在Service線程中執(zhí)行耗時(shí)操作導(dǎo)致ANR。
PS:為什么要使用HandlerThread類窘问?因?yàn)镠andlerThread類的Looper是子線程中的Looper辆童。如果在當(dāng)前類中(IntentService類)直接獲取Looper的話,獲取到的是主線程(Server線程)中的Looper惠赫,如果在IntentServer中創(chuàng)建一個(gè)子線程再獲取Looper的話就相當(dāng)于是又實(shí)現(xiàn)了一次HandlerThread把鉴,所以直接使用HandlerThread。
IntentService有一個(gè)抽象方法onHandlerIntent儿咱,需要在子類中實(shí)現(xiàn)庭砍。
protected void onHandleIntent(@Nullable Intent intent) {}
他的參數(shù)intent來(lái)源于IntentService,就是從其他組件中傳遞過(guò)來(lái)的Intent原模原樣的傳遞給onHandleIntent方法混埠。
每次啟動(dòng)IntentService逗威,都會(huì)通過(guò)mServiceHandler發(fā)送一個(gè)消息,然后傳遞到HandlerThread中處理岔冀。所有傳遞進(jìn)來(lái)的消息會(huì)進(jìn)入Handler的消息隊(duì)列中,等待被Looper檢索并執(zhí)行概耻,等待所有消息都處理完畢后使套,IntentService會(huì)停止服務(wù)。
工作流程如下:
與使用Handler在子線程中操作UI原理相同鞠柄,IntentService是將UI線程中的耗時(shí)操作切換到子線程中執(zhí)行侦高。
以及示例:
public class MyIntentService extends IntentService {
private final String TAG = this.getClass().getSimpleName();
public final static String action = "ACTION";
public MyIntentService() {
super(action);
}
@Override
protected void onHandleIntent(@Nullable Intent intent) {
String name = intent.getStringExtra(action);
Log.e(TAG, "下載文件:" + name);
SystemClock.sleep(3000);
Log.e(TAG, name + "下載完成");
}
@Override
public void onDestroy() {
super.onDestroy();
Log.e(TAG, "銷毀");
}
}
Intent service = new Intent(AsyncTaskActivity.this, MyIntentService.class);
service.putExtra(MyIntentService.action, "一號(hào)文件");
AsyncTaskActivity.this.startService(service);
service.putExtra(MyIntentService.action, "二號(hào)文件");
AsyncTaskActivity.this.startService(service);
01-10 15:48:16.124 30551-30725/com.zx.studyapp E/MyIntentService: 下載文件:一號(hào)文件
01-10 15:48:19.124 30551-30725/com.zx.studyapp E/MyIntentService: 一號(hào)文件下載完成
01-10 15:48:19.126 30551-30725/com.zx.studyapp E/MyIntentService: 下載文件:二號(hào)文件
01-10 15:48:22.126 30551-30725/com.zx.studyapp E/MyIntentService: 二號(hào)文件下載完成
01-10 15:48:22.128 30551-30551/com.zx.studyapp E/MyIntentService: 銷毀
同樣是假設(shè)下載任務(wù),可以看到依次下載第一個(gè)與第二個(gè)文件厌杜,所有任務(wù)執(zhí)行完成之后奉呛,IntentService便自行銷毀。
線程池
其實(shí)這部分屬于Java知識(shí)夯尽,而非Android瞧壮。
線程池有三好:簡(jiǎn)單,重用匙握,不阻塞咆槽。
- 簡(jiǎn)單。對(duì)于大量線程的管理簡(jiǎn)單圈纺。
- 重用秦忿。重用線程池中的線程,減少性能開(kāi)銷蛾娶。
- 不阻塞灯谣。能有效控制最大并發(fā)數(shù),避免大量線程搶占資源導(dǎo)致的系統(tǒng)阻塞蛔琅。
Android的線程池來(lái)源于他爹Java的Executer接口胎许,其實(shí)現(xiàn)為ThreadPoolExecutor 。它提供了一系列的參數(shù)來(lái)配置線程池。
ThreadPoolExecutor
是Android線程池的真正的實(shí)現(xiàn)呐萨,他的構(gòu)造方法提供了一系列的參數(shù)來(lái)配置線程池杀饵。
public ThreadPoolExecutor(
int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory)
corePoolSize:線程池的核心線程數(shù)。默認(rèn)情況下核心線程會(huì)一直存活谬擦,如果將ThreadPoolExecutor的allowCoreThreadTimeOut屬性設(shè)置為true則會(huì)超時(shí)切距,這個(gè)時(shí)間由keepAliveTime給出,超時(shí)的線程會(huì)被終止惨远。
maximumPoolSize:線程池最大線程數(shù)量谜悟,超出任務(wù)會(huì)被阻塞
keepAliveTime:非核心線程超時(shí)時(shí)間,超時(shí)線程會(huì)被回收北秽。
unit:枚舉類型葡幸,用于指定keepAliveTime的時(shí)間單位。常用的類型有TimeUnit.MICROSECONDS(毫秒)贺氓,TimeUnit.SECONDS(秒)蔚叨,TimeUnit.MINUTES(分鐘)等。
workQueue:線程池的任務(wù)隊(duì)列辙培。
threadFactory:線程工廠蔑水,為線程池提供創(chuàng)建新線程的功能。
此外還有一個(gè)不常用的參數(shù)扬蕊,RejectedExecutionHandler handler搀别。當(dāng)線程池?zé)o法執(zhí)行新任務(wù)時(shí),handler的rejectedExecution方法會(huì)被調(diào)用拋出異常尾抑。
線程池執(zhí)行任務(wù)的時(shí)候遵循以下規(guī)則:
- 如果線程池中的線程數(shù)量小于核心線程的總數(shù)歇父,則會(huì)啟動(dòng)一個(gè)核心線程來(lái)執(zhí)行任務(wù)。
- 如果線程池中的線程數(shù)量大于等于核心線程總數(shù)再愈,任務(wù)會(huì)被插入任務(wù)隊(duì)列中等待榜苫。
- 若任務(wù)隊(duì)列已滿,但線程數(shù)量沒(méi)有達(dá)到線程池的最大值翎冲,則會(huì)啟動(dòng)一個(gè)非核心線程來(lái)執(zhí)行任務(wù)单刁。
此處需要注意,啟動(dòng)一個(gè)非核心線程立即執(zhí)行任務(wù)府适,而非從隊(duì)列中讀取一個(gè)任務(wù)羔飞,就是說(shuō),這個(gè)場(chǎng)景下檐春,后來(lái)的任務(wù)可能會(huì)先被執(zhí)行逻淌。看以下例子:
ThreadPoolExecutor executor = new ThreadPoolExecutor(1,//一個(gè)核心線程
10,//10個(gè)非核心線程
1,
TimeUnit.MINUTES,//非核心超時(shí)時(shí)間1分鐘
new LinkedBlockingQueue<Runnable>(2));//任務(wù)隊(duì)列長(zhǎng)度為2
在這個(gè)線程池中疟暖,我們直接執(zhí)行4個(gè)任務(wù)卡儒,從直覺(jué)上來(lái)說(shuō)田柔,應(yīng)該是先來(lái)先執(zhí)行,但是實(shí)際情況不一定骨望。
Runnable run1 = new Runnable() {
@Override
public void run() {
Log.e(TAG, "start,run: 1");
SystemClock.sleep(5000);
Log.e(TAG, "end,run: 1");
}
};
Runnable run2 = ...;
Runnable run3 = ...;
Runnable run4 = ...;
executor.execute(run1);
executor.execute(run2);
executor.execute(run3);
executor.execute(run4);
01-11 17:53:34.263 9549-9690/com.zx.studyapp E/ThreadPoolExecutor: start,run: 1
01-11 17:53:34.263 9549-9691/com.zx.studyapp E/ThreadPoolExecutor: start,run: 4
01-11 17:53:39.264 9549-9690/com.zx.studyapp E/ThreadPoolExecutor: end,run: 1
01-11 17:53:39.264 9549-9691/com.zx.studyapp E/ThreadPoolExecutor: end,run: 4
01-11 17:53:39.264 9549-9690/com.zx.studyapp E/ThreadPoolExecutor: start,run: 2
01-11 17:53:39.264 9549-9691/com.zx.studyapp E/ThreadPoolExecutor: start,run: 3
01-11 17:53:44.264 9549-9691/com.zx.studyapp E/ThreadPoolExecutor: end,run: 3
01-11 17:53:44.264 9549-9690/com.zx.studyapp E/ThreadPoolExecutor: end,run: 2
結(jié)果是硬爆,先執(zhí)行1,4擎鸠,再執(zhí)行2缀磕,3。
PS:學(xué)校學(xué)的東西都還給老師了劣光,這種基礎(chǔ)問(wèn)題都要想好久袜蚕,老師我對(duì)不起你。
- 如果線程數(shù)量也達(dá)到了線程池的最大值绢涡,則此任務(wù)會(huì)被拒絕牲剃,并通過(guò)RejectedExecutionHandler拋出異常。
線程池的分類
最后介紹四種常見(jiàn)的線程池雄可,他們都是同過(guò)配置ThreadPoolExecutor來(lái)實(shí)現(xiàn)的凿傅。
- FixedThreadPool 線程數(shù)量固定的線程池。
是一個(gè)固定線程數(shù)的線程池数苫,它的任務(wù)隊(duì)列大小沒(méi)有限制聪舒,并且沒(méi)有超時(shí)。若提交新任務(wù)時(shí)文判,所有核心線程都處于活動(dòng)狀態(tài),那么新任務(wù)會(huì)進(jìn)行等待室梅,直到有核心線程空出來(lái)戏仓。在線程池被關(guān)閉之前,池中的線程將一直存在亡鼠。
它的優(yōu)勢(shì)是赏殃,可以更加快速的響應(yīng)外界請(qǐng)求。
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
Runnable run = new Runnable() {...};
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(10);
fixedThreadPool.execute(run);
- SingleThreadExecutor 單線程的線程池
是一個(gè)只有一條線程的線程池间涵,它的任務(wù)隊(duì)列沒(méi)有大小限制仁热。
它的優(yōu)勢(shì)是,可以保證所有任務(wù)都是順序執(zhí)行的勾哩,因?yàn)樗腥蝿?wù)都是在同一線程執(zhí)行抗蠢,所以不用考慮線程同步的問(wèn)題。
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
Runnable run = new Runnable() {...};
ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
singleThreadExecutor.execute(run);
關(guān)于SingleThreadExecutor與newFixedThreadPool(1)的區(qū)別思劳,Java文檔上有這么一句話
Unlike the otherwise equivalent {@code newFixedThreadPool(1)} the returned executor is guaranteed not to be reconfigurable to use additional threads.
翻閱stackoverflow的一些帖子迅矛,明白它的大致意思就是,SingleThreadExecutor就有且只能有一條線程潜叛,無(wú)法通過(guò)某些方法變成多條線程的線程池秽褒。比如看下面一個(gè)例子:
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(1);
ThreadPoolExecutor poolExecutor = (ThreadPoolExecutor) fixedThreadPool;
poolExecutor.setCorePoolSize(10);
而SingleThreadExecutor 由于加了包裝類FinalizableDelegatedExecutorService壶硅,隱藏了一些方法,使得無(wú)法配置線程池销斟,就可以保證它永遠(yuǎn)就只有一條線程了庐椒。
- CachedThreadPool 線程數(shù)量不固定的線程池
是一個(gè)線程數(shù)量不固定,根據(jù)需要?jiǎng)?chuàng)建線程的線程池蚂踊。只有非核心線程约谈,最大線程數(shù)可以認(rèn)為是無(wú)窮大。如果有新任務(wù)加入進(jìn)來(lái)悴势,但是沒(méi)有空閑線程窗宇,則會(huì)創(chuàng)建一個(gè)新線程并添加到線程池中。線程超時(shí)時(shí)間為60s,超時(shí)線程會(huì)被回收掉。
它的優(yōu)勢(shì)是互例,對(duì)于執(zhí)行很多短期異步任務(wù)的程序而言喷鸽,這些線程池通常可提高程序性能塔逃。并且在沒(méi)有任務(wù)執(zhí)行時(shí),他幾乎是不占資源的。
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
Runnable run = new Runnable() {...};
ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
cachedThreadPool.execute(run);
可以使用ThreadPoolExecutor構(gòu)造方法創(chuàng)建具有類似屬性但細(xì)節(jié)不同(例如超時(shí)參數(shù))的線程池镰官。
- ScheduledThreadPool 計(jì)劃線程池
是一個(gè)核心線程數(shù)量固定,非核心線程沒(méi)有數(shù)量限制的一個(gè)線程池吗货,且超時(shí)時(shí)間為0s泳唠,執(zhí)行完成會(huì)被立刻回收。
這類線程池主要用于執(zhí)行定時(shí)任務(wù)和重復(fù)任務(wù)宙搬。
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
}
public ScheduledThreadPoolExecutor(int corePoolSize){
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS, new DelayedWorkQueue());
}
Runnable run = new Runnable() {...};
ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(10);
scheduledThreadPool.execute(run);
個(gè)人理解笨腥,難免有錯(cuò)誤紕漏,歡迎指正勇垛。轉(zhuǎn)載請(qǐng)注明出處脖母。