前言
本文已經(jīng)收錄到我的Github個(gè)人博客呜魄,歡迎大佬們光臨寒舍:
需要已經(jīng)具備的知識(shí):
-
Thread
的基本概念及使用 -
AsyncTask
的基本概念及使用
學(xué)習(xí)導(dǎo)圖:
一.為什么要學(xué)習(xí)Thread
?
在Android
中搜贤,幾乎完全沿用了Java
中的線程機(jī)制巷查。線程是最小的調(diào)度單位艺糜,在很多情況下為了使APP
更加流程地運(yùn)行,我們不可能將很多事情都放在主線程上執(zhí)行,這樣會(huì)造成嚴(yán)重卡頓(ANR
),那么這些事情應(yīng)該交給子線程去做矿筝,但對(duì)于一個(gè)系統(tǒng)而言,創(chuàng)建棚贾、銷毀窖维、調(diào)度線程的過程是需要開銷的,所以我們并不能無限量地開啟線程妙痹,那么對(duì)線程的了解就變得尤為重要了铸史。
因此,本篇文章將帶領(lǐng)大家由淺入深细诸,從線程的基礎(chǔ),談到同步機(jī)制陋守,再講到阻塞隊(duì)列震贵,接著提及Android
中的線程形態(tài),最終一覽線程池機(jī)制水评。
話不多說猩系,趕緊跟隨筆者開始奇妙的Thread
之旅吧!
二.核心知識(shí)點(diǎn)歸納
2.1 線程概述
Q1:含義
線程是CPU
調(diào)度的最小單位
注意與進(jìn)程相區(qū)分
Q2:特點(diǎn)
線程是一種受限的系統(tǒng)資源中燥。即線程不可無限制的產(chǎn)生且線程的創(chuàng)建和銷毀都有一定的開銷
- Q:如何避免頻繁創(chuàng)建和銷毀線程所帶來的系統(tǒng)開銷寇甸?
- A:采用線程池,池中會(huì)緩存一定數(shù)量的線程疗涉,進(jìn)而達(dá)到效果(PS:下文將為您詳細(xì)講解)
Q3:分類
- 按用途分為兩類:
主線程:一般一個(gè)進(jìn)程只有一個(gè)主線程拿霉,主要處理界面交互相關(guān)的邏輯
子線程:除主線程之外都是子線程,主要用于執(zhí)行耗時(shí)操作
- 按形態(tài)可分為三類:
AsyncTask
:底層封裝了線程池和Handler
咱扣,便于執(zhí)行后臺(tái)任務(wù)以及在主線程中進(jìn)行UI
操作HandlerThread
:一種具有消息循環(huán)的線程绽淘,其內(nèi)部可使用Handler
IntentService
:一種異步、會(huì)自動(dòng)停止的服務(wù)闹伪,內(nèi)部采用HandlerThread
和Handler
想詳細(xì)了解
Handler
機(jī)制的讀者沪铭,推薦一篇筆者的文章:進(jìn)階之路 | 奇妙的Handler之旅
Q4:如何安全地終止線程壮池?
對(duì)于有多線程開發(fā)經(jīng)驗(yàn)的開發(fā)者,應(yīng)該大多數(shù)在開發(fā)過程中都遇到過這樣的需求杀怠,就是在某種情況下椰憋,希望立即停止一個(gè)線程
比如:做
Android
開發(fā),當(dāng)打開一個(gè)界面時(shí)赔退,需要開啟線程請(qǐng)求網(wǎng)絡(luò)獲取界面的數(shù)據(jù)橙依,但有時(shí)候由于網(wǎng)絡(luò)特別慢,用戶沒有耐心等待數(shù)據(jù)獲取完成就將界面關(guān)閉离钝,此時(shí)就應(yīng)該立即停止線程任務(wù)票编,不然一般會(huì)內(nèi)存泄露,造成系統(tǒng)資源浪費(fèi)卵渴,如果用戶不斷地打開又關(guān)閉界面慧域,內(nèi)存泄露會(huì)累積,最終導(dǎo)致內(nèi)存溢出浪读,APP
閃退所以昔榴,筆者希望能和大家探究下:如何安全地終止線程?
A1:為啥不使用stop
?
Java
官方早已將它廢棄碘橘,不推薦使用
-
stop
是通過立即拋出ThreadDeath
異常互订,來達(dá)到停止線程的目的,此異常拋出有可能發(fā)生在任何一時(shí)間點(diǎn)痘拆,包括在catch
仰禽、finally
等語句塊中,但是此異常并不會(huì)引起程序退出 - 異常拋出纺蛆,導(dǎo)致線程會(huì)釋放全部所持有的鎖吐葵,極可能引起線程安全問題
A2:提供單獨(dú)的取消方法來終止線程
示例DEMO
:
public class MoonRunner implements Runnable {
private long i;
//注意的是這里的變量是用volatile修飾
volatile boolean on = true;
@Override
public void run() {
while (on) {
i++;
}
System.out.println("sTop");
}
//設(shè)置一個(gè)取消的方法
void cancel() {
on = false;
}
}
注意:這里的變量是用
volatile
修飾踊兜,以保證可見性疾党,關(guān)于volatile
的知識(shí),筆者將在下文為您詳細(xì)解析
A3:采用interrupt
來終止線程
Thread
類定義了如下關(guān)于中斷的方法:
原理:
調(diào)用
Thread
對(duì)象的interrupt
函數(shù)并不是立即中斷線程陆蟆,只是將線程中斷狀態(tài)標(biāo)志設(shè)置為true
當(dāng)線程運(yùn)行中有調(diào)用其阻塞的函數(shù)時(shí)字支,阻塞函數(shù)調(diào)用之后凤藏,會(huì)不斷地輪詢檢測(cè)中斷狀態(tài)標(biāo)志是否為
true
,如果為true
堕伪,則停止阻塞并拋出InterruptedException
異常揖庄,同時(shí)還會(huì)重置中斷狀態(tài)標(biāo)志,因此需要在catch
代碼塊中需調(diào)用interrupt
函數(shù)欠雌,使線程再次處于中斷狀態(tài)如果中斷狀態(tài)標(biāo)志為
false
抠艾,則繼續(xù)阻塞,直到阻塞正常結(jié)束
具體的
interrupt
的使用方式可以參考這篇文章:Java線程中斷的正確姿勢(shì)
2.2 同步機(jī)制
2.2.1 volatile
- 有時(shí)候僅僅為了讀寫一個(gè)或者兩個(gè)實(shí)例就使用同步
synchronized
的話桨昙,顯得開銷過大- 而
volatile
為實(shí)例域的同步訪問提供了免鎖的機(jī)制
Q1:先從Java
內(nèi)存模型聊起
-
Java
內(nèi)存模型定義了本地內(nèi)存和主存之間的抽象關(guān)系
- 線程之間的共享變量存儲(chǔ)在主存中
- 每個(gè)線程都有一個(gè)私有的本地內(nèi)存(工作內(nèi)存)检号,本地內(nèi)存中存儲(chǔ)了該線程共享變量的副本腌歉。
- 線程之間通信的步驟
- 線程A將其本地內(nèi)存中更新過的共享變量刷新到主存中去
- 線程B到主存中去讀取線程A之前已更新過的共享變量
Q2:原子性、可見性和有序性
了解多少
a1:原子性Atomicity
:
- 定義:原子性操作就是指這些操作是不可中斷的齐苛,要做一定做完翘盖,要么就沒有執(zhí)行
- 對(duì)基本數(shù)據(jù)類型變量的讀取和賦值操作是原子性操作
注意:這里的賦值操作是指將數(shù)字賦值給某個(gè)變量
下面由DEMO
解釋更加通俗易懂
x=3; //原子性操作
y=x; //非原子性操作 原因:包括2個(gè)操作:先讀取x的值,再將x的值寫入工作內(nèi)存
x++; //非原子性操作 原因:包括3個(gè)操作:讀取x的值凹蜂、對(duì)x的值進(jìn)行加1馍驯、向工作內(nèi)存寫入新值
-
volatile
不支持原子性(想探究原因的,筆者推薦一篇文章:面試官最愛的volatile關(guān)鍵字) - 保證整塊代碼原子性(例如
i++
)的方法:借助于synchronized
和Lock
玛痊,以及并發(fā)包下的atomic
的原子操作類
a2:可見性Visibility
定義:一個(gè)線程修改的結(jié)果汰瘫,另一個(gè)線程馬上就能看到
Java
就是利用volatile
來提供可見性的
原因:當(dāng)一個(gè)變量被
volatile
修飾時(shí),那么對(duì)它的修改會(huì)立刻刷新到主存擂煞,同時(shí)使其它線程的工作內(nèi)存中對(duì)此變量的緩存行失效混弥,因此需要讀取該變量時(shí),會(huì)去內(nèi)存中讀取新值
- 其實(shí)通過
synchronized
和Lock
也能夠保證可見性对省,但是synchronized
和Lock
的開銷都更大
a3:有序性Ordering
- 指令重排序的定義:大多數(shù)現(xiàn)代微處理器都會(huì)采用將指令亂序執(zhí)行的方法, 在條件允許的情況下, 直接運(yùn)行當(dāng)前有能力立即執(zhí)行的后續(xù)指令, 避開獲取下一條指令所需數(shù)據(jù)時(shí)造成的等待
- 什么時(shí)候不進(jìn)行指令重排序:
- 符合數(shù)據(jù)依賴性:
//x對(duì)a有依賴 a = 1; x = a;
as-if-serial
語義:不管怎么重排序, 單線程程序的執(zhí)行結(jié)果不能被改變- 程序順序原則
- 如果A
happens-before
B- 如果B
happens-before
C- 那么A
happens-before
C這就是
happens-before
傳遞性
-
volatile
通過禁止指令重排序的方式來保證有序性
Q3:應(yīng)用場(chǎng)景有哪些蝗拿?
- 狀態(tài)量標(biāo)記
線程的終止的時(shí)候的狀態(tài)控制,示例
DEMO
如前文
DCL
避免指令重排序:
假定創(chuàng)建一個(gè)對(duì)象需要:
- 申請(qǐng)內(nèi)存
- 初始化
instance
指向分配的那塊內(nèi)存上面的2和3操作是有可能重排序的, 如果3重排序到2的前面, 這時(shí)候2操作還沒有執(zhí)行,
instance!=null
, 當(dāng)然不是安全的
class Singleton{
private volatile static Singleton instance = null;
private Singleton() {}
public static Singleton getInstance() {
if(instance==null) {
synchronized (Singleton.class) {
if(instance==null)
instance = new Singleton();
}
}
return instance;
}
}
Q4:原理:
- 如果把加入
volatile
關(guān)鍵字的代碼和未加入volatile
關(guān)鍵字的代碼都生成匯編代碼蒿涎,會(huì)發(fā)現(xiàn)加入volatile
關(guān)鍵字的代碼會(huì)多出一個(gè)lock
前綴指令 -
lock
前綴指令實(shí)際相當(dāng)于一個(gè)內(nèi)存屏障哀托,內(nèi)存屏障提供了以下功能:
- 重排序時(shí)不能把后面的指令重排序到內(nèi)存屏障之前的位置
- 使得本
CPU
的Cache
寫入內(nèi)存- 寫入動(dòng)作也會(huì)引起別的
CPU
或者別的內(nèi)核無效化其Cache
,相當(dāng)于讓新寫入的值對(duì)別的線程可見
2.2.2 重入鎖與條件對(duì)象
synchronized
關(guān)鍵字自動(dòng)為我們提供了鎖以及相關(guān)的條件劳秋,大多數(shù)需要顯式鎖的時(shí)候仓手,使用synchronized
非常方便,但是當(dāng)我們了解了重入鎖和條件對(duì)象時(shí)玻淑,能更好地理解synchronized
和阻塞隊(duì)列
Q1:重入鎖的定義
- 可重入鎖指的是可重復(fù)可遞歸調(diào)用的鎖嗽冒,在外層使用鎖之后,在內(nèi)層仍然可以使用岁忘,并且不發(fā)生死鎖辛慰,這樣的鎖就叫做可重入鎖
-
ReentrantLock
和synchronized
都是可重入鎖
重復(fù)調(diào)用鎖的
DEMO
如下:
public class ReentrantTest implements Runnable {
public synchronized void get() {
System.out.println(Thread.currentThread().getName());
set();
}
public synchronized void set() {
System.out.println(Thread.currentThread().getName());
}
public void run() {
get();
}
public static void main(String[] args) {
ReentrantTest rt = new ReentrantTest();
for(;;){
new Thread(rt).start();
}
}
}
Q2:什么是條件對(duì)象Condition
区匠?
- 條件對(duì)象來管理那些已經(jīng)獲得了一個(gè)鎖但是卻不能做有用工作的線程干像,條件對(duì)象又被稱作條件變量
- 一般要配合
ReentrantLock
使用,用Condition.await()
可以阻塞當(dāng)前線程驰弄,并放棄鎖
Q3:下面說明重入鎖與條件對(duì)象如何協(xié)同使用
- 用支付寶轉(zhuǎn)賬的例子(支付寶打錢麻汰,狗頭.jpg)
- 場(chǎng)景是這樣的:
//轉(zhuǎn)賬的方法
public void transfer(int from, int to, int amount){
//alipay是ReentrantLock的實(shí)例
alipay.lock();
try{
//當(dāng)要轉(zhuǎn)給別人的錢大于你所擁有的錢的時(shí)候,調(diào)用Condition的await可以阻塞當(dāng)前線程戚篙,并放棄鎖
while(accounts[from] < amount){
condition.await();
}
...//一系列轉(zhuǎn)賬的操作
//阻塞狀態(tài)解除,進(jìn)入可運(yùn)行狀態(tài)
condition.signalAll();
}
finally{
alipay.unlock();
}
}
想要更深一步了解重入鎖的讀者五鲫,可以看下這篇文章:究竟什么是可重入鎖?
2.2.3 synchronized
Q1:synchronized
有哪幾種實(shí)現(xiàn)方式岔擂?
- 同步代碼塊
- 同步方法
Q2:synchronized
與ReentrantLock
的關(guān)系
- 兩者都是重入鎖
- 兩者有些方法互相對(duì)應(yīng)
wait
等價(jià)于condition.await()
notifyAll
等價(jià)于condition.signalAll()
Q3:使用場(chǎng)景對(duì)比
類型 | 使用場(chǎng)景 |
---|---|
阻塞隊(duì)列 | 一般實(shí)現(xiàn)同步的時(shí)候使用 |
同步方法 | 如果同步方法適合你的程序 |
同步代碼塊 | 不太建議使用位喂,因?yàn)椴僮髌饋砣菀壮鲥e(cuò) |
Lock/Condition |
需要使用Lock/Condition 的獨(dú)有特性時(shí) |
2.3 阻塞隊(duì)列
為了更好地理解線程池的知識(shí)浪耘,我們需要了解下阻塞隊(duì)列
Q1:定義
- 阻塞隊(duì)列
BlockingQueue
是一個(gè)支持兩個(gè)附加操作的隊(duì)列。這兩個(gè)附加的操作是:
- 在隊(duì)列為空時(shí)塑崖,獲取元素的線程會(huì)阻塞七冲,直到隊(duì)列變?yōu)榉强?/li>
- 當(dāng)隊(duì)列滿時(shí),存儲(chǔ)元素的線程會(huì)阻塞规婆,直到隊(duì)列變?yōu)榉菨M
Q2:使用場(chǎng)景:
阻塞隊(duì)列常用于生產(chǎn)者和消費(fèi)者的場(chǎng)景澜躺,生產(chǎn)者是往隊(duì)列里添加元素的線程,消費(fèi)者是從隊(duì)列里拿元素的線程抒蚜。阻塞隊(duì)列就是生產(chǎn)者存放元素的容器掘鄙,而消費(fèi)者也只從容器里拿元素。
Q3:核心方法
方法\處理方式 | 拋出異常 | 返回特殊值 | 一直阻塞 | 超時(shí)退出 |
---|---|---|---|---|
插入方法 | add(e) |
offer(e) |
put(e) |
offer(e,time,unit) |
移除方法 | remove() |
poll() |
take() |
poll(time,unit) |
檢查方法 | element() |
peek() |
不可用 | 不可用 |
Q4:JAVA
中的阻塞隊(duì)列
名稱 | 含義 |
---|---|
ArrayBlockingQueue |
由數(shù)組結(jié)構(gòu)組成的有界阻塞隊(duì)列(最常用) |
LinkedBlockingQueue |
由鏈表結(jié)構(gòu)組成的有界阻塞隊(duì)列(最常用)注意:一定要指定大小 |
PriorityBlockingQueue |
支持優(yōu)先級(jí)排序的無界阻塞隊(duì)列嗡髓。默認(rèn)自然升序排列 |
DelayQueue |
支持延時(shí)獲取元素的無界阻塞隊(duì)列操漠。 |
SynchronousQueue |
不存儲(chǔ)元素的阻塞隊(duì)列(可以看成是一個(gè)傳球手,負(fù)責(zé)把生產(chǎn)者線程處理的數(shù)據(jù)直接傳遞給消費(fèi)者線程) |
LinkedTransferQueue |
由鏈表結(jié)構(gòu)組成的無界阻塞隊(duì)列 |
LinkedBlockingDeque |
由鏈表結(jié)構(gòu)組成的雙向阻塞隊(duì)列(雙向隊(duì)列指的是可以從隊(duì)列的兩端插入和移出元素) |
Q5:實(shí)現(xiàn)原理:
- 底層利用了
ReentrantLock
&Condition
來實(shí)現(xiàn)自動(dòng)加鎖和解鎖的功能 - 如果想詳細(xì)了解阻塞隊(duì)列實(shí)現(xiàn)原理的源碼器贩,筆者推薦一篇文章:Android并發(fā)學(xué)習(xí)之阻塞隊(duì)列
2.4 Android
中的線程形態(tài)
2.4.1 AsyncTask
Q1:定義:一種輕量級(jí)的異步任務(wù)類
在
Android
中實(shí)現(xiàn)異步任務(wù)機(jī)制有兩種方式:Handler和AsyncTask
Handler
機(jī)制存在的問題:代碼相對(duì)臃腫颅夺;多任務(wù)同時(shí)執(zhí)行時(shí)不易精確控制線程。- 引入
AsyncTask
的好處:創(chuàng)建異步任務(wù)更簡(jiǎn)單蛹稍,直接繼承它可方便實(shí)現(xiàn)后臺(tái)異步任務(wù)的執(zhí)行和進(jìn)度的回調(diào)更新UI
吧黄,而無需編寫任務(wù)線程和Handler
實(shí)例就能完成相同的任務(wù)。
Q2:五個(gè)核心方法:
方法 | 運(yùn)行線程 | 調(diào)用時(shí)刻 | 作用 |
---|---|---|---|
onPreExecute() |
主線程 | 在異步任務(wù)執(zhí)行之前被調(diào)用 | 可用于進(jìn)行一些界面上的初始化操作 |
doInBackground() |
子線程 | 異步任務(wù)執(zhí)行時(shí) | 可用于處理所有的耗時(shí)任務(wù)唆姐。若需要更新UI 需調(diào)用 publishProgress()
|
onProgressUpdate() |
主線程 | 調(diào)用publishProgress() 之后 |
可利用方法中攜帶的參數(shù)如Progress 來對(duì)UI 進(jìn)行相應(yīng)地更新 |
onPostExecute() |
主線程 | 在異步任務(wù)執(zhí)行完畢并通過return 語句返回時(shí)被調(diào)用 |
可利用方法中返回的數(shù)據(jù)來進(jìn)行一些UI 操作 |
onCancelled() |
主線程 | 當(dāng)異步任務(wù)被取消時(shí)被調(diào)用 | 可用于做界面取消的更新 |
注意:
- 不要直接調(diào)用上述方法
AsyncTask
對(duì)象必須在主線程創(chuàng)建
Q3:開始和結(jié)束異步任務(wù)的方法
execute()
- 必須在主線程中調(diào)用
- 作用:表示開始一個(gè)異步任務(wù)
- 注意:一個(gè)異步對(duì)象只能調(diào)用一次
execute()
方法
cancel()
- 必須在主線程中調(diào)用
- 作用:表示停止一個(gè)異步任務(wù)
Q4:工作原理:
- 內(nèi)部有一個(gè)靜態(tài)的
Handler
對(duì)象即InternalHandler
作用:將執(zhí)行環(huán)境從線程池切換到主線程拗慨;通過它來發(fā)送任務(wù)執(zhí)行的進(jìn)度以及執(zhí)行結(jié)束等消息
注意:必須在主線程中創(chuàng)建
- 內(nèi)部有兩個(gè)線程池:
SerialExecutor
:用于任務(wù)的排隊(duì),默認(rèn)是串行的線程池THREAD_POOL_EXECUTOR
:用于真正執(zhí)行任務(wù)
- 排隊(duì)執(zhí)行過程:
- 把參數(shù)
Params
封裝為FutureTask
對(duì)象奉芦,相當(dāng)于Runnable
- 調(diào)用
SerialExecutor.execute()
將FutureTask
插入到任務(wù)隊(duì)列tasks
- 若沒有正在活動(dòng)的
AsyncTask
任務(wù)赵抢,則就會(huì)執(zhí)行下一個(gè)AsyncTask
任務(wù)。執(zhí)行完畢后會(huì)繼續(xù)執(zhí)行其他任務(wù)直到所有任務(wù)都完成声功。即默認(rèn)使用串行方式執(zhí)行任務(wù)烦却。
執(zhí)行流程圖:
注意:AsyncTask
不適用于進(jìn)行特別耗時(shí)的后臺(tái)任務(wù),而是建議用線程池
如果想要了解具體源碼的讀者先巴,筆者推薦一篇文章:Android AsyncTask完全解析其爵,帶你從源碼的角度徹底理解
2.4.2 HandlerThread
Q1:定義:
-
HandlerThread
是一個(gè)線程類,它繼承自Thread
- 與普通
Thread
的區(qū)別:具有消息循環(huán)的效果伸蚯。原理:
- 內(nèi)部
HandlerThread.run()
方法中有Looper
摩渺,通過Looper.prepare()
來創(chuàng)建消息隊(duì)列,并通過Looper.loop()
來開啟消息循環(huán)
Q2:實(shí)現(xiàn)方法
- 實(shí)例化一個(gè)
HandlerThread
對(duì)象剂邮,參數(shù)是該線程的名稱 - 通過
HandlerThread.start()
開啟線程 - 實(shí)例化一個(gè)
Handler
并傳入HandlerThread
中的Looper
對(duì)象摇幻,使得與HandlerThread
綁定 - 利用
Handler
即可執(zhí)行異步任務(wù) - 當(dāng)不需要
HandlerThread
時(shí),通過HandlerThread.quit()
/quitSafely()
方法來終止線程的執(zhí)行
private HandlerThread myHandlerThread ;
private Handler handler ;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//實(shí)例化HandlerThread
myHandlerThread = new HandlerThread("myHandler") ;
//開啟HandlerThread
myHandlerThread.start();
//將Handler對(duì)象與HandlerThread線程綁定
handler =new Handler(myHandlerThread.getLooper()){
@Override
publicvoid handleMessage(Message msg) {
super.handleMessage(msg);
// 這里接收Handler發(fā)來的消息,運(yùn)行在handler_thread線程中
//TODO...
}
};
//在主線程給Handler發(fā)送消息
handler.sendEmptyMessage(1) ;
new Thread(new Runnable() {
@Override
publicvoid run() {
//在子線程給Handler發(fā)送數(shù)據(jù)
handler.sendEmptyMessage(2) ;
}
}).start();
}
@Override
protected void onDestroy() {
super.onDestroy();
//終止HandlerThread運(yùn)行
myHandlerThread.quit() ;
}
Q3:用途
- 進(jìn)行串行異步通信
- 構(gòu)造
IntentService
- 方便實(shí)現(xiàn)在子線程與子線程直接的通信
Q4:原理:
- 實(shí)際就是
HandlerThread.run()
里面封裝了Looper.prepare()
和Looper.loop()
绰姻,以便能在子線程中使用Handler
- 同時(shí)枉侧,
HandlerThread.getLooper()
中使用了wait()
和synchronized代碼塊
,當(dāng)Looper==NULL
的時(shí)候狂芋,鎖住了當(dāng)前的對(duì)象棵逊,那什么時(shí)候喚醒等待呢?當(dāng)然是在初始化完該線程關(guān)聯(lián)Looper
對(duì)象的地方银酗,也就是run()
想了解源碼的話辆影,筆者推薦一篇文章:淺析HandlerThread
2.4.3 IntentService
Q1:定義:
IntentService
是一個(gè)繼承自Service
的抽象類
Q2:優(yōu)點(diǎn):
- 相比于線程:由于是服務(wù),優(yōu)先級(jí)比線程高黍特,更不容易被系統(tǒng)殺死蛙讥。因此較適合執(zhí)行一些高優(yōu)先級(jí)的后臺(tái)任務(wù)
- 相比于普通
Service
:可自動(dòng)創(chuàng)建子線程來執(zhí)行任務(wù),且任務(wù)執(zhí)行完畢后自動(dòng)退出
Q3:使用方法
- 新建類并繼承
IntentService
灭衷,重寫onHandleIntent()
次慢,該方法:
- 運(yùn)行在子線程,因此可以進(jìn)行一些耗時(shí)操作
- 作用:從
Intent
參數(shù)中區(qū)分具體的任務(wù)并執(zhí)行這些任務(wù)
- 在配置文件中進(jìn)行注冊(cè)
- 在活動(dòng)中利用
Intent
實(shí)現(xiàn)IntentService
的啟動(dòng):
Intent intent = new Intent(this, MyService.class);
intent.putExtra("xxx",xxx);
startService(intent);//啟動(dòng)服務(wù)
注意:無需手動(dòng)停止服務(wù)翔曲,
onHandleIntent()
執(zhí)行結(jié)束之后迫像,IntentService
會(huì)自動(dòng)停止。
Q4:工作原理
- 在
IntentService.onCreate()
里創(chuàng)建一個(gè)Thread
對(duì)象即HandlerThread
瞳遍,利用其內(nèi)部的Looper
會(huì)實(shí)例化一個(gè)ServiceHandler
- 任務(wù)請(qǐng)求的
Intent
會(huì)被封裝到Message
并通過ServiceHandler
發(fā)送給Looper
的MessageQueue
闻妓,最終在HandlerThread
中執(zhí)行 - 在
ServiceHandler.handleMessage()
中會(huì)調(diào)用IntentService.onHandleIntent()
,可在該方法中處理后臺(tái)任務(wù)的邏輯,執(zhí)行完畢后會(huì)調(diào)用stopSelf()
掠械,以實(shí)現(xiàn)自動(dòng)停止
下面繼續(xù)來研究下:將Intent
傳遞給服務(wù) & 依次插入到工作隊(duì)列中的流程
如果對(duì)
IntentService
的具體源碼感興趣的話由缆,筆者推薦一篇文章:Android多線程:IntentService用法&源碼分析
2.5 線程池
Q1:優(yōu)點(diǎn)
- 重用線程池中的線程,避免線程的創(chuàng)建和銷毀帶來的性能消耗
- 有效控制線程池的最大并發(fā)數(shù)猾蒂,避免大量的線程之間因互相搶占系統(tǒng)資源而導(dǎo)致阻塞現(xiàn)象
- 進(jìn)行線程管理均唉,提供定時(shí)/循環(huán)間隔執(zhí)行等功能
Q2:構(gòu)造方法分析
- 線程池的概念來源:Java中的
Executor
,它是一個(gè)接口- 線程池的真正實(shí)現(xiàn):
ThreadPoolExecutor
肚菠,提供一系列參數(shù)來配置線程池
//構(gòu)造參數(shù)
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
-
corePoolSize
:核心線程數(shù)
默認(rèn)情況下舔箭,核心線程會(huì)在線程中一直存活
當(dāng)設(shè)置
ThreadPoolExecutor
的allowCoreThreadTimeOut
屬性為A.
true
:表示核心線程閑置超過超時(shí)時(shí)長,會(huì)被回收B.
false
: 表示核心線程不會(huì)被回收蚊逢,會(huì)在線程池中一直存活
-
maximumPoolSize
:最大線程數(shù)
當(dāng)活動(dòng)線程數(shù)達(dá)到這個(gè)數(shù)值后层扶,后續(xù)的任務(wù)將會(huì)被阻塞
-
keepAliveTime
:非核心線程超時(shí)時(shí)間
- 超過這個(gè)時(shí)長,閑置的非核心線程就會(huì)被回收
- 當(dāng)設(shè)置
ThreadPoolExecutor
的allowCoreThreadTimeTout
屬性為true
時(shí)时捌,keepAliveTime
對(duì)核心線程同樣有效
-
unit
:用于指定keepAliveTime
參數(shù)的時(shí)間單位
單位有:
TimeUnit.MILLISECONDS
怒医、TimeUnit.SECONDS
炉抒、TimeUnit.MINUTES
等奢讨;
-
workQueue
:任務(wù)隊(duì)列
通過線程池的
execute()
方法提交的Runnable
對(duì)象會(huì)存儲(chǔ)在這個(gè)參數(shù)中
-
threadFactory
:線程工廠,可創(chuàng)建新線程
一個(gè)接口,只有一個(gè)方法
Thread newThread(Runnable r)
-
handler
:在線程池?zé)o法執(zhí)行新任務(wù)時(shí)進(jìn)行調(diào)度
Q3:ThreadPoolExecutor
的默認(rèn)工作策略
? Q4:線程池的分類
名稱 | 含義 | 特點(diǎn) |
---|---|---|
FixThreadPool |
線程數(shù)量固定的線程池拿诸,所有線程都是核心線程扒袖,當(dāng)線程空閑時(shí)不會(huì)被回收 | 能快速響應(yīng)外界請(qǐng)求 |
CachedThreadPool |
線程數(shù)量不定的線程池(最大線程數(shù)為Integer.MAX_VALUE),只有非核心線程亩码,空閑線程有超時(shí)機(jī)制季率,超時(shí)回收 | 適合于執(zhí)行大量的耗時(shí)較少的任務(wù) |
ScheduledThreadPool |
核心線程數(shù)量固定,非核心線程數(shù)量不定 | 定時(shí)任務(wù)和固定周期的任務(wù) |
SingleThreadExecutor |
只有一個(gè)核心線程描沟,可確保所有的任務(wù)都在同一個(gè)線程中按順序執(zhí)行 | 無需處理線程同步問題 |
三.再聊聊AsyTask
的不足
AsyncTask
看似十分美好飒泻,但實(shí)際上存在著非常多的不足,這些不足使得它逐漸退出了歷史舞臺(tái)吏廉,因此如今已經(jīng)被RxJava
泞遗、協(xié)程
等新興框架所取代(PS:有機(jī)會(huì)希望能和大家一起探究下RxJava
的源碼)
- 生命周期
AsyncTask
沒有與Activity
、Fragment
的生命周期綁定席覆,即使Activity
被銷毀史辙,它的doInBackground
任務(wù)仍然會(huì)繼續(xù)執(zhí)行
- 取消任務(wù)
AsyncTask
的cancel
方法的參數(shù)mayInterruptIfRunning
存在的意義不大,并且它無法保證任務(wù)一定能取消佩伤,只能盡快讓任務(wù)取消(比如如果正在進(jìn)行一些無法打斷的操作時(shí)聊倔,任務(wù)就仍然會(huì)運(yùn)行)
- 內(nèi)存泄漏
- 由于它沒有與
Activity
等生命周期進(jìn)行綁定,因此它的生命周期仍然可能比Activity
長- 如果將它作為
Activity
的非static
內(nèi)部類生巡,則它會(huì)持有Activity
的引用耙蔑,導(dǎo)致Activity
的內(nèi)存無法釋放。(PS:與Handler
的內(nèi)存泄漏問題類似孤荣,參考文章:進(jìn)階之路 | 奇妙的Handler之旅)
- 并行/串行
由于
AsyncTask
的串行和并行執(zhí)行在多個(gè)版本上都進(jìn)行了修改纵潦,所以當(dāng)多個(gè)AsyncTask
依次執(zhí)行時(shí),它究竟是串行還是并行執(zhí)行取決于用戶手機(jī)的版本垃环。具體修改如下:A.
Android 1.6
之前:各個(gè)AsyncTask
按串行的順序進(jìn)行執(zhí)行B.
Android 1.6
--Android 3.0
:由于設(shè)計(jì)者認(rèn)為串行執(zhí)行效率太低邀层,因此改為了并行執(zhí)行,最多五個(gè)AsyncTask
同時(shí)執(zhí)行C.
Android 3.0
之后:由于之前的改動(dòng)遂庄,很多應(yīng)用出現(xiàn)了并發(fā)問題寥院,因此引入SerialExecutor
改回了串行執(zhí)行,但對(duì)并行執(zhí)行進(jìn)行了支持
如果文章對(duì)您有一點(diǎn)幫助的話涛目,希望您能點(diǎn)一下贊秸谢,您的點(diǎn)贊,是我前進(jìn)的動(dòng)力
本文參考鏈接:
- 《Android 開發(fā)藝術(shù)探索》
- 《Android 進(jìn)階之光》
- 進(jìn)階之路 | 奇妙的Handler之旅
- Java線程中斷的正確姿勢(shì)
- 面試官最愛的volatile關(guān)鍵字
- 究竟什么是可重入鎖霹肝?
- Android并發(fā)學(xué)習(xí)之阻塞隊(duì)列
- Android AsyncTask完全解析估蹄,帶你從源碼的角度徹底理解
- 淺析HandlerThread
- Android多線程:IntentService用法&源碼分析
- 要點(diǎn)提煉|開發(fā)藝術(shù)之線程
- AsyncTask 源碼解析:Android 自帶的異步任務(wù)工具