多線程開發(fā)在android開發(fā)中非常常見,多線程相關(guān)問題也是開發(fā)人員面試的必考題晕换,那么今天我們就來聊聊Android中的多線程守问。
本文的要點如下:
- 線程概述
- Java中的線程
- Android中的線程
- AsyncTask
- HandlerThread
- IntentService
- 線程池
- 線程池的優(yōu)勢
- 線程池的用法
- 線程池使用實例
- 4種常見的線程池
- 總結(jié)
線程概述
線程是CPU調(diào)度的最小單元。同時線程也是一種受限的系統(tǒng)資源项鬼。即線程不可無限制的產(chǎn)生且線程的創(chuàng)建和銷毀都有一定的開銷侦厚。
那么耻陕,如何避免頻繁創(chuàng)建和銷毀線程所帶來的系統(tǒng)開銷?
這就要用到線程池了刨沦,線程池中會緩存一定數(shù)量的線程诗宣,進而通過復(fù)用以及管理線程提高系統(tǒng)的穩(wěn)定性。
Android中的線程分為兩類:
主線程:用于處理界面交互相關(guān)的邏輯想诅,一般一個應(yīng)用只有一個召庞。
子線程:除主線程之外都是子線程,主要用于執(zhí)行耗時操作侧蘸,防止主線程阻塞造成ANR裁眯。
Java中的線程
Android中線程形態(tài)
除了Java本身的線程類鹉梨,Android中還封裝了多種異步類讳癌,便于Android中多線程的開發(fā)。
AsyncTask
AsyncTask是一個Android已經(jīng)封裝好的輕量級異步類存皂,AsyncTask本身是抽象類晌坤,即使用時需繼承實現(xiàn)子類。相信大家在平時的開發(fā)中應(yīng)該多多少少都用過旦袋,AsyncTask本身封裝的非常簡潔骤菠,使用起來也非常方便。
AsyncTask是一個抽象泛型類疤孕。
public abstract class AsyncTask<Params, Progress, Result>
其中商乎,三個泛型類型參數(shù)的含義如下:
Params:開始異步任務(wù)執(zhí)行時傳入的參數(shù)類型;
Progress:異步任務(wù)執(zhí)行過程中祭阀,返回下載進度值的類型鹉戚;
Result:異步任務(wù)執(zhí)行完成后鲜戒,返回的結(jié)果類型。
如果AsyncTask確定不需要傳遞具體參數(shù)抹凳,那么這三個泛型參數(shù)可以用Void來代替遏餐。
AsyncTask子類的實現(xiàn):
public class MyTask extends AsyncTask<String, Integer, Integer> {
//執(zhí)行線程任務(wù)前的操作,可以用于初始化參數(shù)
@Override
protected void onPreExecute() {
super.onPreExecute();
}
//在子線程中運行,用于處理耗時任務(wù)
//這里隨便寫了個更新進度的邏輯
@Override
protected Integer doInBackground(String... strings) {
//每0.1秒更新一次進度赢底,直到100
for(int i = 0;i<100;i++){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
publishProgress(i);
}
return null;
}
//主線程中進行失都,利用參數(shù)中的數(shù)值就可以對界面元素進行相應(yīng)的更新,
@Override
protected void onProgressUpdate(Integer... values) {
super.onProgressUpdate(values);
}
//可以利用返回的數(shù)據(jù)來進行一些UI操作幸冻,在主線程中進行
@Override
protected void onPostExecute(Integer integer) {
super.onPostExecute(integer);
}
}
以上四個方法即為AsyncTask的核心方法粹庞。
onPreExecute():這個方法會在后臺任務(wù)開始執(zhí)行之前調(diào)用,在主線程執(zhí)行嘁扼⌒帕福可以用于初始化參數(shù)或進行一些界面上的初始化工作,比如顯示一個進度條對話框等趁啸。
doInBackground(String... strings):這個方法中的所有代碼都會在子線程中運行强缘,這里就是我們執(zhí)行耗時任務(wù)的地方。
任務(wù)一旦完成就可以通過return語句來將任務(wù)的執(zhí)行結(jié)果進行返回不傅,如果AsyncTask的第三個泛型參數(shù)指定的是Void旅掂,就可以不返回任務(wù)執(zhí)行結(jié)果。由于是在子線程中访娶,因此這個方法中是不可以進行UI操作的商虐。
onProgressUpdate(Integer... values) :在這個方法中可以對UI進行操作,在主線程中進行崖疤,當(dāng)在doInBackground()方法中調(diào)用了publishProgress(Integer... values)方法后秘车,這個方法就很快會被調(diào)用,方法中攜帶的參數(shù)就是在后臺任務(wù)中傳遞過來的劫哼,利用參數(shù)中的數(shù)值就可以對界面元素進行相應(yīng)的更新叮趴。
onPostExecute(Integer integer):doInBackground()方法執(zhí)行完畢并通過return語句進行返回時,這個方法就很快會被調(diào)用权烧。return返回的數(shù)據(jù)會作為參數(shù)傳遞到此方法中眯亦,可以利用返回的數(shù)據(jù)來進行一些UI操作,在主線程中進行般码,比如展示任務(wù)執(zhí)行的結(jié)果妻率。
上面幾個方法的調(diào)用順序:onPreExecute() --> doInBackground() --> publishProgress() --> onProgressUpdate() --> onPostExecute()。
如果不需要更新進度信息則為:onPreExecute() --> doInBackground() --> onPostExecute()板祝。
另外除了上面四個方法宫静,AsyncTask還提供了onCancelled()方法,它同樣在主線程中執(zhí)行,用AsyncTask中的cancel()方法取消任務(wù)時孤里,onCancelled()會被調(diào)用温技,這個時候onPostExecute()不會被調(diào)用,但是要注意的是扭粱,AsyncTask中的cancel()方法并不是真正去取消任務(wù)舵鳞,只是設(shè)置這個任務(wù)為取消狀態(tài),我們需要在doInBackground()判斷終止任務(wù)琢蛤。就好比想要終止一個線程蜓堕,調(diào)用interrupt()方法,只是進行標記為中斷博其,需要在線程內(nèi)部進行標記判斷然后中斷線程套才。
HandlerThread
HandlerThread也是一個Android已封裝好的輕量級異步類。從名字上也不難推斷出慕淡,實際上背伴,HandlerThread本質(zhì)上是通過繼承Thread類和封裝Handler類的使用,從而使得創(chuàng)建新線程和與其他線程進行通信變得更加方便易用峰髓。HandlerThread主要是用來執(zhí)行多個耗時操作傻寂,而不需要多次開啟線程。
通過繼承Thread類携兵,快速地創(chuàng)建1個帶有Looper對象的新工作線程疾掰;
通過封裝Handler類,快速創(chuàng)建Handler與其他線程進行通信徐紧。
HandlerThread的使用方法如下:
//1.創(chuàng)建HandlerThread的實例對象静檬,傳入的參數(shù)為線程的名字
HandlerThread mhandlerThread = new HandlerThread("MyHandlerThread");
//2.啟動HandlerThread線程
mhandlerThread.start();
//3.將HandlerThread與Handler綁定在一起
Handler workHandler = new Handler(mhandlerThread.getLooper()){
@Override
public void handleMessage(Message msg) {
//這里會在子線程中執(zhí)行
super.handleMessage(msg);
}
};
發(fā)送message的方法和普通的Handler沒什么區(qū)別:
workHandler.sendMessage(msg);
結(jié)束線程用mHandlerThread.quit()方法,會停止線程的消息循環(huán)并级。
IntentService
Android里的一個封裝類拂檩,繼承四大組件之一的Service。
詳見:Android基礎(chǔ)之Service
線程池
在開發(fā)中嘲碧,我們往往會通過new Thread來開啟一個子線程稻励,待子線程操作完成以后通過Handler切換到主線程中運行。這么以來我們無法管理我們所創(chuàng)建的子線程呀潭,并且無限制的創(chuàng)建子線程钉迷,它們相互之間競爭至非,很有可能由于占用過多資源而導(dǎo)致死機或者OOM钠署。因此Java中為我們提供了線程池來管理我們所創(chuàng)建的線程。
線程池的優(yōu)勢:
- 降低系統(tǒng)資源消耗荒椭,通過重用已存在的線程谐鼎,降低線程創(chuàng)建和銷毀造成的消耗;
- 提高系統(tǒng)響應(yīng)速度趣惠,當(dāng)有任務(wù)到達時狸棍,無需等待新線程的創(chuàng)建便能立即執(zhí)行身害;
- 方便線程并發(fā)數(shù)的管控,線程若是無限制的創(chuàng)建草戈,不僅會額外消耗大量系統(tǒng)資源塌鸯,更是占用過多資源而阻塞系統(tǒng)或oom等狀況,從而降低系統(tǒng)的穩(wěn)定性唐片。線程池能有效管控線程丙猬,統(tǒng)一分配、調(diào)優(yōu)费韭,提供資源使用率茧球;
- 更強大的功能,線程池提供了定時星持、定期以及可控線程數(shù)等功能的線程池抢埋,使用方便簡單。
線程池的使用方法
其實使用線程池大致可以分為3步:
- 實例化線程池
- 添加任務(wù)
- 關(guān)閉線程池
實例化線程池
線程池的構(gòu)造方法如下:
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
corePoolSize:線程池中的核心線程數(shù)督暂,默認情況下揪垄,核心線程一直存活在線程池中,即便他們在線程池中處于閑置狀態(tài)逻翁。除非我們將ThreadPoolExecutor的allowCoreThreadTimeOut屬性設(shè)為true的時候福侈,這時候處于閑置的核心線程在等待新任務(wù)到來時會有超時策略,這個超時時間由keepAliveTime來指定卢未。一旦超過所設(shè)置的超時時間肪凛,閑置的核心線程就會被終止。
maximumPoolSize:線程池中所容納的最大線程數(shù)辽社,如果活動的線程達到這個數(shù)值以后伟墙,后續(xù)的新任務(wù)將會被阻塞。包含核心線程數(shù)+非核心線程數(shù)滴铅。
keepAliveTime:非核心線程閑置時的超時時長戳葵,對于非核心線程,閑置時間超過這個時間就會被回收汉匙。當(dāng)allowCoreThreadTimeOut屬性設(shè)為true的時候拱烁,這個時間同樣對核心線程產(chǎn)生有效。
unit:用于指定keepAliveTime參數(shù)的時間單位噩翠。是一個枚舉對象
workQueue:線程池中保存等待執(zhí)行的任務(wù)的阻塞隊列戏自。通過線程池中的execute方法提交的Runable對象都會存儲在該隊列中。
隊列 特點 ArrayBlockingQueue 基于數(shù)組實現(xiàn)的有界的阻塞隊列,該隊列按照FIFO(先進先出)原則對隊列中的元素進行排序伤锚。 LinkedBlockingQueue 基于鏈表實現(xiàn)的阻塞隊列擅笔,該隊列按照FIFO(先進先出)原則對隊列中的元素進行排序。 SynchronousQueue 內(nèi)部沒有任何容量的阻塞隊列。在它內(nèi)部沒有任何的緩存空間猛们。對于SynchronousQueue中的數(shù)據(jù)元素只有當(dāng)我們試著取走的時候才可能存在念脯。 PriorityBlockingQueue 具有優(yōu)先級的無限阻塞隊列。 實現(xiàn)BlockingQueue接口 自定義阻塞隊列弯淘。 threadFactory:線程工廠绿店,為線程池提供新線程的創(chuàng)建。ThreadFactory是一個接口庐橙,里面只有一個newThread方法惯吕。 默認為DefaultThreadFactory類。
handler:實現(xiàn)RejectedExecutionHandler接口的對象怕午,接口里面只有一個rejectedExecution方法废登。當(dāng)任務(wù)隊列已滿并且線程池中的活動線程已經(jīng)達到所限定的最大值或者是無法成功執(zhí)行任務(wù),這時候ThreadPoolExecutor會調(diào)用RejectedExecutionHandler中的rejectedExecution方法郁惜。在ThreadPoolExecutor中有四個內(nèi)部類實現(xiàn)了RejectedExecutionHandler接口堡距。在線程池中它默認是AbortPolicy,在無法處理新任務(wù)時拋出RejectedExecutionException異常兆蕉。
添加任務(wù)
構(gòu)造完ThreadPoolExecutor對象之后羽戒,就可以向線程池中添加任務(wù)了。
添加任務(wù)有兩種方法:execute和submit虎韵;
最簡單的就是execute()方法易稠,只需要實現(xiàn)一個Runnable接口就行。但是當(dāng)我們使用execute來提交任務(wù)時包蓝,由于execute方法沒有返回值驶社,所以說我們也就無法判定任務(wù)是否被線程池執(zhí)行成功。
submit()方法稍微復(fù)雜些测萎,當(dāng)我們使用submit來提交任務(wù)時,它會返回一個future亡电,我們就可以通過這個future來判斷任務(wù)是否執(zhí)行成功,還可以通過future的get方法來獲取返回值硅瞧。如果子線程任務(wù)沒有完成份乒,get方法會阻塞住直到任務(wù)完成,而使用get(long timeout, TimeUnit unit)方法則會阻塞一段時間后立即返回腕唧,這時候有可能任務(wù)并沒有執(zhí)行完或辖。我們可以用future.isDone()方法來判斷任務(wù)是否執(zhí)行完畢,之后再取返回值枣接。
//Future<?> submit(Runnable task)
Future<?> future = threadpool.submit(new Runnable() {
@Override
public void run() {
System.out.println("子線程id :" + Thread.currentThread().getId());//輸出當(dāng)前線程id
}
});
//Future<T> submit(Callable<T> task)
Future<Integer> future2 = threadpool.submit(new Callable<Integer>() {
@Override
public Integer call() throws Exception{
Thread.sleep(3000);
System.out.println("子線程id :" + Thread.currentThread().getId());//輸出當(dāng)前線程id
System.out.println("future2 任務(wù)結(jié)束");
return 315;
}
});
關(guān)閉線程池
可以調(diào)用線程池的shutdown()或shutdownNow()方法來關(guān)閉線程池
shutdown原理:將線程池狀態(tài)設(shè)置成SHUTDOWN狀態(tài)颂暇,然后中斷所有沒有正在執(zhí)行任務(wù)的線程。
shutdownNow原理:將線程池的狀態(tài)設(shè)置成STOP狀態(tài)月腋,然后中斷所有任務(wù)(包括正在執(zhí)行的)的線程蟀架,并返回等待執(zhí)行任務(wù)的列表。
一般情況下建議調(diào)用shutdown()關(guān)閉線程池榆骚;若任務(wù)不一定要執(zhí)行完片拍,則調(diào)用shutdownNow()。
線程池的使用實例:
//打印主線程id
System.out.println("主線程id : " + Thread.currentThread().getId());//輸出主線程id
//實例化線程池
ExecutorService threadpool = new ThreadPoolExecutor(5, 10, 10, TimeUnit.SECONDS, new LinkedBlockingQueue<>());
//構(gòu)建任務(wù)
Runnable task = new Runnable() {
@Override
public void run() {
System.out.println("子線程id :" + Thread.currentThread().getId());//輸出當(dāng)前線程id
}
};
//提交任務(wù)
//方式1,execute
threadpool.execute(task);
//方式2,submit
//Runnable任務(wù)
Future<?> future = threadpool.submit(task);
//Callable任務(wù)
Future<Integer> future2 = threadpool.submit(new Callable<Integer>() {
@Override
public Integer call() throws Exception{
Thread.sleep(5000);
System.out.println(Thread.currentThread().getId());//輸出當(dāng)前線程id
System.out.println("future2 任務(wù)結(jié)束");
return 315;
}
});
while(!future2.isDone()) {
System.out.println("任務(wù)future2還未執(zhí)行完畢");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("任務(wù)future2執(zhí)行完畢");
try {
//獲取返回值
System.out.println("任務(wù)返回值為" + future2.get());
} catch (Exception e) {
e.printStackTrace();
}
//關(guān)閉線程池
threadpool.shutdown();
以上代碼的執(zhí)行結(jié)果為:
主線程id : 1
子線程id :11
子線程id :12
任務(wù)future2還未執(zhí)行完畢
任務(wù)future2還未執(zhí)行完畢
任務(wù)future2還未執(zhí)行完畢
13
future2 任務(wù)結(jié)束
任務(wù)future2執(zhí)行完畢
任務(wù)返回值為315
4種常見的線程池
根據(jù)參數(shù)的不同配置妓肢,Java中最常見的線程池有4類:
- 定長線程池(FixedThreadPool)
- 定時線程池(ScheduledThreadPool )
- 可緩存線程池(CachedThreadPool)
- 單線程化線程池(SingleThreadExecutor)
他們都是直接或者間接配置ThreadPoolExecutor來實現(xiàn)他們各自的功能捌省。這四種線程池可以通過Executors類獲取。
FixedThreadPool
定長線程池只有核心線程碉钠,線程數(shù)量固定纲缓,任務(wù)隊列無大小限制(超出的線程任務(wù)會在隊列中等待)。通過 Executors.newFixedThreadPool() 創(chuàng)建喊废。
//實例化定長線程池祝高,參數(shù)為核心線程數(shù)量
ExecutorService myFixedThreadPool = Executors.newFixedThreadPool(5);//設(shè)置線程數(shù)量固定為5
//構(gòu)建任務(wù)
Runnable task = new Runnable() {
@Override
public void run() {
System.out.println("子線程id :" + Thread.currentThread().getId());//輸出當(dāng)前線程id
}
};
//提交任務(wù)
myFixedThreadPool.execute(task);
//關(guān)閉線程池
myFixedThreadPool.shutdown();
newFixedThreadPool只有核心線程,并且這些線程都不會被回收污筷,因此它能夠更快速的響應(yīng)外界請求工闺。
ScheduledThreadPool
定時線程池核心線程數(shù)量固定、非核心線程數(shù)量無限制(閑置時會馬上回收)瓣蛀,通過Executors.newScheduledThreadPool()創(chuàng)建陆蟆。
//實例化定時線程池,參數(shù)為核心線程數(shù)量
ScheduledExecutorService myScheduledThreadPool = Executors.newScheduledThreadPool(5);//核心線程數(shù)為5
//構(gòu)建任務(wù)
Runnable task = new Runnable() {
@Override
public void run() {
System.out.println("子線程id :" + Thread.currentThread().getId());//輸出當(dāng)前線程id
}
};
//用schedule提交任務(wù)惋增,參數(shù)為任務(wù)叠殷,延遲時間,時間單位
//eg: 延遲1s后執(zhí)行
myScheduledThreadPool.schedule(task, 1, TimeUnit.SECONDS);
//用scheduleAtFixedRate提交任務(wù)诈皿,參數(shù)為任務(wù)林束,延遲時間,間隔時間稽亏,時間單位
//eg:延遲10ms后诊县、每隔1000ms執(zhí)行一次任務(wù)
myScheduledThreadPool.scheduleAtFixedRate(task,10,1000,TimeUnit.MILLISECONDS);
//關(guān)閉線程池
myScheduledThreadPool.shutdown();
ScheduledThreadPool執(zhí)行任務(wù)使用的是如下方法:
schedule(Runnable command, long delay, TimeUnit unit)
:延遲一定時間后執(zhí)行Runnable任務(wù);schedule(Callable callable, long delay, TimeUnit unit)
:延遲一定時間后執(zhí)行Callable任務(wù)措左;scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit)
:延遲一定時間后依痊,以間隔period時間的頻率周期性地執(zhí)行任務(wù);scheduleWithFixedDelay(Runnable command, long initialDelay, long delay,TimeUnit unit)
:與scheduleAtFixedRate()方法很類似怎披,但是不同的是scheduleWithFixedDelay()方法的周期時間間隔是以上一個任務(wù)執(zhí)行結(jié)束到下一個任務(wù)開始執(zhí)行的間隔胸嘁,而scheduleAtFixedRate()方法的周期時間間隔是以上一個任務(wù)開始執(zhí)行到下一個任務(wù)開始執(zhí)行的間隔,也就是說scheduleWithFixedDelay方法執(zhí)行的任務(wù)的開始時間其實是不可預(yù)知的凉逛,和前一個任務(wù)的執(zhí)行時間有關(guān)性宏,而scheduleAtFixedRate方法執(zhí)行任務(wù)的觸發(fā)時間都是可預(yù)知的,是固定的状飞。
CachedThreadPool
可緩存線程池的核心線程數(shù)為0毫胜, 線程池的最大線程數(shù)Integer.MAX_VALUE书斜,具備超時機制,當(dāng)線程處于閑置狀態(tài)超過60秒的時候便會被回收酵使,當(dāng)線程池中的線程都處于活動狀態(tài)的時候荐吉,線程池就會創(chuàng)建一個新的線程來處理任務(wù)。若是整個線程池的線程都處于閑置狀態(tài)超過60秒以后口渔,線程池中是不存在任何線程的样屠,所以這時候它幾乎不占用任何的系統(tǒng)資源。
//實例化定時線程池
ExecutorService myCachedThreadPool = Executors.newCachedThreadPool();
//構(gòu)建任務(wù)
Runnable task = new Runnable() {
@Override
public void run() {
System.out.println("子線程id :" + Thread.currentThread().getId());//輸出當(dāng)前線程id
}
};
//提交任務(wù)
myCachedThreadPool.execute(task);
//關(guān)閉線程池
myCachedThreadPool.shutdown();
使用方法很簡單缺脉,無需配置參數(shù)痪欲,可以直接添加任務(wù)。
SingleThreadExecutor
單線程化線程池攻礼,在這個線程池中只有一個核心線程业踢,對于任務(wù)隊列沒有大小限制,也就意味著一個任務(wù)處于活動狀態(tài)時礁扮,其他任務(wù)都會在任務(wù)隊列中排隊等候依次執(zhí)行陨亡,因此不需要處理線程同步的問題。
ExecutorService mySingleThreadExecutor = Executors.newSingleThreadExecutor();
//構(gòu)建任務(wù)
Runnable task = new Runnable() {
@Override
public void run() {
System.out.println("子線程id :" + Thread.currentThread().getId());//輸出當(dāng)前線程id
}
};
//提交任務(wù)
mySingleThreadExecutor.execute(task);
//關(guān)閉線程池
mySingleThreadExecutor.shutdown();
單線程化線程池不適合并發(fā)但可能引起IO阻塞性及影響UI線程響應(yīng)的操作深员,如數(shù)據(jù)庫操作负蠕,文件操作等。
總結(jié)
線程的目的都是執(zhí)行功能倦畅,主要實現(xiàn)的也就是Runnable和Callable兩個接口遮糖,基本的使用還是不難的,但是要想理解原理叠赐,還需要更深入的去學(xué)習(xí)源碼欲账。有關(guān)線程的內(nèi)容有很多,本次只是進行了大致的總結(jié)芭概,之后有時間會進行更加詳細的總結(jié)赛不。