前段時(shí)間遇到這樣一個(gè)問(wèn)題速侈,有人問(wèn)微信朋友圈的上傳圖片的功能怎么做才能讓用戶的等待時(shí)間較短显设,比如說(shuō)一下上傳9張圖片,我說(shuō)這還不簡(jiǎn)單草冈,我們項(xiàng)目中就有篙程,直接串行依次往服務(wù)器上傳就好啊枷畏。然而别厘,這并不是最優(yōu)方案虱饿,其實(shí)我們可以通過(guò)線程池的用法,來(lái)開啟多個(gè)線程同時(shí)處理上傳的任務(wù)触趴。這樣如果網(wǎng)絡(luò)帶寬允許的情況下氮发,那么這種方式一定會(huì)比串行的效率更高,其實(shí)這樣的場(chǎng)景很常見冗懦,只是平時(shí)我們沒加注意罷了爽冕,下面就來(lái)介紹一下線程池相關(guān)的用法:
說(shuō)到線程池,無(wú)非就是存放線程的地方披蕉,那么線程的概念颈畸,我們都很熟悉乌奇,因?yàn)樵陂_發(fā)時(shí)候或多或少都會(huì)用到線程,而通常創(chuàng)建線程有兩種方式:
1眯娱、繼承Thread類
2礁苗、實(shí)現(xiàn)Runnable接口
雖說(shuō)這兩種方式都可以創(chuàng)建出一個(gè)線程,不過(guò)它們之間還是有一點(diǎn)區(qū)別的徙缴,主要區(qū)別在于在多線程訪問(wèn)同一資源的情況下试伙,用Runnable接口創(chuàng)建的線程可以處理同一資源,而用Thread類創(chuàng)建的線程則各自獨(dú)立處理于样,各自擁有自己的資源疏叨。
所以,在Java中大多數(shù)多線程程序都是通過(guò)實(shí)現(xiàn)Runnable來(lái)完成的穿剖,而對(duì)于Android來(lái)說(shuō)也不例外蚤蔓,當(dāng)涉及到需要開啟線程去完成某件事時(shí),我們都會(huì)這樣寫:
new Thread(new Runnable() {
@Override
public void run() {
//do sth .
}
}).start();
這段代碼創(chuàng)建了一個(gè)線程并執(zhí)行糊余,它在任務(wù)結(jié)束后GC會(huì)自動(dòng)回收該線程昌粤,一切看起來(lái)如此美妙,是的啄刹,它在線程并發(fā)不多的程序中確實(shí)不錯(cuò)涮坐,而假如這個(gè)程序有很多地方需要開啟大量線程來(lái)處理任務(wù),那么如果還是用上述的方式去創(chuàng)建線程處理的話誓军,那么將導(dǎo)致系統(tǒng)的性能表現(xiàn)的非常糟糕袱讹,更別說(shuō)在內(nèi)存有限的移動(dòng)設(shè)備上,主要的影響如下:
1昵时、線程的創(chuàng)建和銷毀都需要時(shí)間捷雕,當(dāng)有大量的線程創(chuàng)建和銷毀時(shí),那么這些時(shí)間的消耗則比較明顯壹甥,將導(dǎo)致性能上的缺失
2救巷、大量的線程創(chuàng)建、執(zhí)行和銷毀是非常耗cpu和內(nèi)存的句柠,這樣將直接影響系統(tǒng)的吞吐量浦译,導(dǎo)致性能急劇下降,如果內(nèi)存資源占用的比較多溯职,還很可能造成OOM
3精盅、大量的線程的創(chuàng)建和銷毀很容易導(dǎo)致GC頻繁的執(zhí)行,從而發(fā)生內(nèi)存抖動(dòng)現(xiàn)象谜酒,而發(fā)生了內(nèi)存抖動(dòng)叹俏,對(duì)于移動(dòng)端來(lái)說(shuō),最大的影響就是造成界面卡頓
而針對(duì)上述所描述的問(wèn)題僻族,解決的辦法歸根到底就是:重用已有的線程粘驰,從而減少線程的創(chuàng)建屡谐。
所以這就涉及到線程池(ExecutorService)的概念了,線程池的基本作用就是進(jìn)行線程的復(fù)用蝌数,下面將具體介紹線程池的使用
ExecutorService
通過(guò)上述分析康嘉,我們知道了通過(guò)new Thread().start()方式創(chuàng)建線程去處理任務(wù)的弊端,而為了解決這些問(wèn)題籽前,Java為我們提供了ExecutorService線程池來(lái)優(yōu)化和管理線程的使用
使用線程池管理線程的優(yōu)點(diǎn)
1亭珍、線程的創(chuàng)建和銷毀由線程池維護(hù),一個(gè)線程在完成任務(wù)后并不會(huì)立即銷毀枝哄,而是由后續(xù)的任務(wù)復(fù)用這個(gè)線程肄梨,從而減少線程的創(chuàng)建和銷毀,節(jié)約系統(tǒng)的開銷
2挠锥、線程池旨在線程的復(fù)用众羡,這就可以節(jié)約我們用以往的方式創(chuàng)建線程和銷毀所消耗的時(shí)間,減少線程頻繁調(diào)度的開銷蓖租,從而節(jié)約系統(tǒng)資源粱侣,提高系統(tǒng)吞吐量
3、在執(zhí)行大量異步任務(wù)時(shí)提高了性能
4蓖宦、Java內(nèi)置的一套ExecutorService線程池相關(guān)的api齐婴,可以更方便的控制線程的最大并發(fā)數(shù)、線程的定時(shí)任務(wù)稠茂、單線程的順序執(zhí)行等
ExecutorService簡(jiǎn)介
通常來(lái)說(shuō)我們說(shuō)到線程池第一時(shí)間想到的就是它:ExecutorService柠偶,它是一個(gè)接口,其實(shí)如果要從真正意義上來(lái)說(shuō)睬关,它可以叫做線程池的服務(wù)诱担,因?yàn)樗峁┝吮姸嘟涌赼pi來(lái)控制線程池中的線程,而真正意義上的線程池就是:ThreadPoolExecutor电爹,它實(shí)現(xiàn)了ExecutorService接口蔫仙,并封裝了一系列的api使得它具有線程池的特性,其中包括工作隊(duì)列丐箩、核心線程數(shù)摇邦、最大線程數(shù)等。
線程池:ThreadPoolExecutor
既然線程池就是ThreadPoolExecutor雏蛮,所以我們要?jiǎng)?chuàng)建一個(gè)線程池只需要new ThreadPoolExecutor(…);就可以創(chuàng)建一個(gè)線程池涎嚼,而如果這樣創(chuàng)建線程池的話,我們需要配置一堆東西挑秉,非常麻煩,我們可以看一下它的構(gòu)造方法就知道了:
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {...}
所以苔货,官方也不推薦使用這種方法來(lái)創(chuàng)建線程池犀概,而是推薦使用Executors的工廠方法來(lái)創(chuàng)建線程池立哑,Executors類是官方提供的一個(gè)工廠類,它里面封裝好了眾多功能不一樣的線程池姻灶,從而使得我們創(chuàng)建線程池非常的簡(jiǎn)便铛绰,主要提供了如下五種功能不一樣的線程池:
1、newFixedThreadPool() :
作用:該方法返回一個(gè)固定線程數(shù)量的線程池产喉,該線程池中的線程數(shù)量始終不變捂掰,即不會(huì)再創(chuàng)建新的線程,也不會(huì)銷毀已經(jīng)創(chuàng)建好的線程曾沈,自始自終都是那幾個(gè)固定的線程在工作这嚣,所以該線程池可以控制線程的最大并發(fā)數(shù)。
栗子:假如有一個(gè)新任務(wù)提交時(shí)塞俱,線程池中如果有空閑的線程則立即使用空閑線程來(lái)處理任務(wù)姐帚,如果沒有,則會(huì)把這個(gè)新任務(wù)存在一個(gè)任務(wù)隊(duì)列中障涯,一旦有線程空閑了罐旗,則按FIFO方式處理任務(wù)隊(duì)列中的任務(wù)。
2唯蝶、newCachedThreadPool() :
作用:該方法返回一個(gè)可以根據(jù)實(shí)際情況調(diào)整線程池中線程的數(shù)量的線程池九秀。即該線程池中的線程數(shù)量不確定,是根據(jù)實(shí)際情況動(dòng)態(tài)調(diào)整的粘我。
栗子:假如該線程池中的所有線程都正在工作颤霎,而此時(shí)有新任務(wù)提交,那么將會(huì)創(chuàng)建新的線程去處理該任務(wù)涂滴,而此時(shí)假如之前有一些線程完成了任務(wù)友酱,現(xiàn)在又有新任務(wù)提交,那么將不會(huì)創(chuàng)建新線程去處理柔纵,而是復(fù)用空閑的線程去處理新任務(wù)缔杉。那么此時(shí)有人有疑問(wèn)了,那這樣來(lái)說(shuō)該線程池的線程豈不是會(huì)越集越多搁料?其實(shí)并不會(huì)或详,因?yàn)榫€程池中的線程都有一個(gè)“保持活動(dòng)時(shí)間”的參數(shù),通過(guò)配置它郭计,如果線程池中的空閑線程的空閑時(shí)間超過(guò)該“保存活動(dòng)時(shí)間”則立刻停止該線程霸琴,而該線程池默認(rèn)的“保持活動(dòng)時(shí)間”為60s。
3昭伸、newSingleThreadExecutor() :
作用:該方法返回一個(gè)只有一個(gè)線程的線程池梧乘,即每次只能執(zhí)行一個(gè)線程任務(wù),多余的任務(wù)會(huì)保存到一個(gè)任務(wù)隊(duì)列中,等待這一個(gè)線程空閑选调,當(dāng)這個(gè)線程空閑了再按FIFO方式順序執(zhí)行任務(wù)隊(duì)列中的任務(wù)夹供。
4、newScheduledThreadPool() :
作用:該方法返回一個(gè)可以控制線程池內(nèi)線程定時(shí)或周期性執(zhí)行某任務(wù)的線程池仁堪。
5哮洽、newSingleThreadScheduledExecutor() :
作用:該方法返回一個(gè)可以控制線程池內(nèi)線程定時(shí)或周期性執(zhí)行某任務(wù)的線程池。只不過(guò)和上面的區(qū)別是該線程池大小為1弦聂,而上面的可以指定線程池的大小鸟辅。
好了,寫了一堆來(lái)介紹這五種線程池的作用莺葫,接下來(lái)就是獲取這五種線程池匪凉,通過(guò)Executors的工廠方法來(lái)獲取:
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(5);
ExecutorService singleThreadPool = Executors.newSingleThreadExecutor();
ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);
ScheduledExecutorService singleThreadScheduledPool = Executors.newSingleThreadScheduledExecutor();
我們可以看到通過(guò)Executors的工廠方法來(lái)創(chuàng)建線程池極其簡(jiǎn)便徙融,其實(shí)它的內(nèi)部還是通過(guò)new ThreadPoolExecutor(…)的方式創(chuàng)建線程池的洒缀,我們看一下這些工廠方法的內(nèi)部實(shí)現(xiàn):
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue());
}
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue()));
}
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue());
}
我們可以清楚的看到這些方法的內(nèi)部實(shí)現(xiàn)都是通過(guò)創(chuàng)建一個(gè)ThreadPoolExecutor對(duì)象來(lái)創(chuàng)建的,正所謂萬(wàn)變不離其宗欺冀,所以我們要了解線程池還是得了解ThreadPoolExecutor這個(gè)線程池類树绩,其中由于和定時(shí)任務(wù)相關(guān)的線程池比較特殊(newScheduledThreadPool()、newSingleThreadScheduledExecutor())隐轩,它們創(chuàng)建的線程池內(nèi)部實(shí)現(xiàn)是由ScheduledThreadPoolExecutor這個(gè)類實(shí)現(xiàn)的饺饭,而ScheduledThreadPoolExecutor是繼承于ThreadPoolExecutor擴(kuò)展而成的,所以本質(zhì)還是一樣的职车,只不過(guò)多封裝了一些定時(shí)任務(wù)相關(guān)的api瘫俊,所以我們主要就是要了解ThreadPoolExecutor,從構(gòu)造方法開始:
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {//...}
我們可以看到它構(gòu)造方法的參數(shù)比較多悴灵,有七個(gè)扛芽,下面一一來(lái)說(shuō)明這些參數(shù)的作用:
corePoolSize:線程池中的核心線程數(shù)量
maximumPoolSize:線程池中的最大線程數(shù)量
keepAliveTime:這個(gè)就是上面說(shuō)到的“保持活動(dòng)時(shí)間“,上面只是大概說(shuō)明了一下它的作用积瞒,不過(guò)它起作用必須在一個(gè)前提下川尖,就是當(dāng)線程池中的線程數(shù)量超過(guò)了corePoolSize時(shí),它表示多余的空閑線程的存活時(shí)間茫孔,即:多余的空閑線程在超過(guò)keepAliveTime時(shí)間內(nèi)沒有任務(wù)的話則被銷毀叮喳。而這個(gè)主要應(yīng)用在緩存線程池中
unit:它是一個(gè)枚舉類型,表示keepAliveTime的單位缰贝,常用的如:TimeUnit.SECONDS(秒)馍悟、TimeUnit.MILLISECONDS(毫秒)
workQueue:任務(wù)隊(duì)列,主要用來(lái)存儲(chǔ)已經(jīng)提交但未被執(zhí)行的任務(wù)剩晴,不同的線程池采用的排隊(duì)策略不一樣锣咒,稍后再講
threadFactory:線程工廠,用來(lái)創(chuàng)建線程池中的線程,通常用默認(rèn)的即可
handler:通常叫做拒絕策略宠哄,1壹将、在線程池已經(jīng)關(guān)閉的情況下 2嗤攻、任務(wù)太多導(dǎo)致最大線程數(shù)和任務(wù)隊(duì)列已經(jīng)飽和毛嫉,無(wú)法再接收新的任務(wù) 。在上面兩種情況下妇菱,只要滿足其中一種時(shí)承粤,在使用execute()來(lái)提交新的任務(wù)時(shí)將會(huì)拒絕,而默認(rèn)的拒絕策略是拋一個(gè)RejectedExecutionException異常
上面的參數(shù)理解起來(lái)都比較簡(jiǎn)單闯团,不過(guò)workQueue這個(gè)任務(wù)隊(duì)列卻要再次說(shuō)明一下辛臊,它是一個(gè)BlockingQueue對(duì)象,而泛型則限定它是用來(lái)存放Runnable對(duì)象的房交,剛剛上面講了彻舰,不同的線程池它的任務(wù)隊(duì)列實(shí)現(xiàn)肯定是不一樣的,所以候味,保證不同線程池有著不同的功能的核心就是這個(gè)workQueue的實(shí)現(xiàn)了刃唤,細(xì)心的會(huì)發(fā)現(xiàn)在剛剛的用來(lái)創(chuàng)建線程池的工廠方法中,針對(duì)不同的線程池傳入的workQueue也不一樣白群,下面我總結(jié)一下這五種線程池分別用的是什么BlockingQueue:
1尚胞、newFixedThreadPool()—>LinkedBlockingQueue
2、newSingleThreadExecutor()—>LinkedBlockingQueue
3帜慢、newCachedThreadPool()—>SynchronousQueue
4笼裳、newScheduledThreadPool()—>DelayedWorkQueue
5、newSingleThreadScheduledExecutor()—>DelayedWorkQueue
這些隊(duì)列分別表示:
LinkedBlockingQueue:無(wú)界的隊(duì)列
SynchronousQueue:直接提交的隊(duì)列
DelayedWorkQueue:等待隊(duì)列
當(dāng)然實(shí)現(xiàn)了BlockingQueue接口的隊(duì)列還有:ArrayBlockingQueue(有界的隊(duì)列)粱玲、PriorityBlockingQueue(優(yōu)先級(jí)隊(duì)列)躬柬。這些隊(duì)列的詳細(xì)作用就不多介紹了。
線程池ThreadPoolExecutor的使用
使用線程池抽减,其中涉及到一個(gè)極其重要的方法允青,即:
execute(Runnable command)
該方法意為執(zhí)行給定的任務(wù),該任務(wù)處理可能在新的線程胯甩、已入池的線程或者正調(diào)用的線程昧廷,這由ThreadPoolExecutor的實(shí)現(xiàn)決定。
newFixedThreadPool
創(chuàng)建一個(gè)固定線程數(shù)量的線程池偎箫,示例為:
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(4);
for (int i = 1; i 10; i++) {
final int index = i;
fixedThreadPool.execute(new Runnable() {
@Override
public void run() {
String threadName = Thread.currentThread().getName();
Log.v("zxy", "線程:"+threadName+",正在執(zhí)行第" + index + "個(gè)任務(wù)");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
上述代碼木柬,我們創(chuàng)建了一個(gè)線程數(shù)為3的固定線程數(shù)量的線程池,同理該線程池支持的線程最大并發(fā)數(shù)也是3淹办,而我模擬了10個(gè)任務(wù)讓它處理眉枕,執(zhí)行的情況則是首先執(zhí)行前三個(gè)任務(wù),后面7個(gè)則依次進(jìn)入任務(wù)隊(duì)列進(jìn)行等待,執(zhí)行完前三個(gè)任務(wù)后速挑,再通過(guò)FIFO的方式從任務(wù)隊(duì)列中取任務(wù)執(zhí)行谤牡,直到最后任務(wù)都執(zhí)行完畢。
為了體現(xiàn)出線程的復(fù)用姥宝,我特地在Log中加上了當(dāng)前線程的名稱翅萤,效果為:
newSingleThreadExecutor
創(chuàng)建一個(gè)只有一個(gè)線程的線程池,每次只能執(zhí)行一個(gè)線程任務(wù)腊满,多余的任務(wù)會(huì)保存到一個(gè)任務(wù)隊(duì)列中套么,等待線程處理完再依次處理任務(wù)隊(duì)列中的任務(wù),示例為:
ExecutorService singleThreadPool = Executors.newSingleThreadExecutor();
for (int i = 1; i 10; i++) {
final int index = i;
singleThreadPool.execute(new Runnable() {
@Override
public void run() {
String threadName = Thread.currentThread().getName();
Log.v("zxy", "線程:"+threadName+",正在執(zhí)行第" + index + "個(gè)任務(wù)");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
代碼還是差不多碳蛋,只不過(guò)改了線程池的實(shí)現(xiàn)方式胚泌,效果我想大家都知道,即依次一個(gè)一個(gè)的處理任務(wù)肃弟,而且都是復(fù)用一個(gè)線程玷室,效果為:
分9步來(lái)完成,每一步實(shí)際上執(zhí)行一個(gè)操作
其實(shí)我們通過(guò)newSingleThreadExecutor()和newFixedThreadPool()的方法發(fā)現(xiàn)笤受,創(chuàng)建一個(gè)singleThreadExecutorPool實(shí)際上就是創(chuàng)建一個(gè)核心線程數(shù)和最大線程數(shù)都為1的fixedThreadPool穷缤。
newCachedThreadPool
創(chuàng)建一個(gè)可以根據(jù)實(shí)際情況調(diào)整線程池中線程的數(shù)量的線程池,示例為:
ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
for (int i = 1; i 10; i++) {
final int index = i;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
cachedThreadPool.execute(new Runnable() {
@Override
public void run() {
String threadName = Thread.currentThread().getName();
Log.v("zxy", "線程:" + threadName + ",正在執(zhí)行第" + index + "個(gè)任務(wù)");
try {
long time = index * 500;
Thread.sleep(time);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
為了體現(xiàn)該線程池可以自動(dòng)根據(jù)實(shí)現(xiàn)情況進(jìn)行線程的重用感论,而不是一味的創(chuàng)建新的線程去處理任務(wù)绅项,我設(shè)置了每隔1s去提交一個(gè)新任務(wù),這個(gè)新任務(wù)執(zhí)行的時(shí)間也是動(dòng)態(tài)變化的比肄,所以快耿,效果為:
newScheduledThreadPool
創(chuàng)建一個(gè)可以定時(shí)或者周期性執(zhí)行任務(wù)的線程池,示例
ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(3);
//延遲2秒后執(zhí)行該任務(wù)
scheduledThreadPool.schedule(new Runnable() {
@Override
public void run() {
}
}, 2, TimeUnit.SECONDS);
//延遲1秒后芳绩,每隔2秒執(zhí)行一次該任務(wù)
scheduledThreadPool.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
}
}, 1, 2, TimeUnit.SECONDS);
newSingleThreadScheduledExecutor
創(chuàng)建一個(gè)可以定時(shí)或者周期性執(zhí)行任務(wù)的線程池掀亥,該線程池的線程數(shù)為1,示例為:
ScheduledExecutorService singleThreadScheduledPool = Executors.newSingleThreadScheduledExecutor();
//延遲1秒后妥色,每隔2秒執(zhí)行一次該任務(wù)
singleThreadScheduledPool.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
String threadName = Thread.currentThread().getName();
Log.v("zxy", "線程:" + threadName + ",正在執(zhí)行");
}
},1,2,TimeUnit.SECONDS);
實(shí)際上這個(gè)和上面的沒什么太大區(qū)別搪花,只不過(guò)是線程池內(nèi)線程數(shù)量的不同,效果為:
每隔2秒就會(huì)執(zhí)行一次該任務(wù)
自定義線程池ThreadPoolExecutor
Java內(nèi)置只為我們提供了五種常用的線程池嘹害,一般來(lái)說(shuō)這足夠用了撮竿,不過(guò)有時(shí)候我們也可以根據(jù)需求來(lái)自定義我們自己的線程池,而要自定義不同功能的線程池笔呀,上面我們也說(shuō)了線程池功能的不同歸根到底還是內(nèi)部的BlockingQueue實(shí)現(xiàn)不同幢踏,所以,我們要實(shí)現(xiàn)我們自己相要的線程池许师,就必須從BlockingQueue的實(shí)現(xiàn)上做手腳房蝉,而上面也說(shuō)了BlockingQueue的實(shí)現(xiàn)類有多個(gè)僚匆,那么這次我們就選用PriorityBlockingQueue來(lái)實(shí)現(xiàn)一個(gè)功能是按任務(wù)的優(yōu)先級(jí)來(lái)處理的線程池。
1搭幻、首先我們創(chuàng)建一個(gè)基于PriorityBlockingQueue實(shí)現(xiàn)的線程池咧擂,為了測(cè)試方便,我這里把核心線程數(shù)量設(shè)置為3檀蹋,如下:
1
ExecutorService priorityThreadPool = new ThreadPoolExecutor(3,3,0L,TimeUnit.SECONDS,new PriorityBlockingQueue());
2松申、然后創(chuàng)建一個(gè)實(shí)現(xiàn)Runnable接口的類,并向外提供一個(gè)抽象方法供我們實(shí)現(xiàn)自定義功能续扔,并實(shí)現(xiàn)Comparable接口攻臀,實(shí)現(xiàn)這個(gè)接口主要就是進(jìn)行優(yōu)先級(jí)的比較焕数,代碼如下:
public abstract class PriorityRunnable implements Runnable, Comparable {
private int priority;
public PriorityRunnable(int priority) {
if (priority 0)
throw new IllegalArgumentException();
this.priority = priority;
}
@Override
public int compareTo(PriorityRunnable another) {
int my = this.getPriority();
int other = another.getPriority();
return my 1 : my > other ? -1 : 0;
}
@Override
public void run() {
doSth();
}
public abstract void doSth();
public int getPriority() {
return priority;
}
}
3纱昧、使用我們自己的PriorityRunnable提交任務(wù)宙刘,整體代碼如下:
ExecutorService priorityThreadPool = new ThreadPoolExecutor(3, 3, 0L, TimeUnit.SECONDS, new PriorityBlockingQueue());
for (int i = 1; i 10; i++) {
final int priority = i;
priorityThreadPool.execute(new PriorityRunnable(priority) {
@Override
public void doSth() {
String threadName = Thread.currentThread().getName();
Log.v("zxy", "線程:" + threadName + ",正在執(zhí)行優(yōu)先級(jí)為:" + priority + "的任務(wù)");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
測(cè)試效果
我們看下剛剛自定義的線程池是否達(dá)到了我們想要的功能唠叛,即根據(jù)任務(wù)的優(yōu)先級(jí)進(jìn)行優(yōu)先處理任務(wù)魁袜,效果如下:
可以從執(zhí)行結(jié)果中看出腾节,由于核心線程數(shù)設(shè)置為3寓搬,剛開始時(shí)瓢娜,系統(tǒng)有3個(gè)空閑線程渤涌,所以無(wú)須使用任務(wù)隊(duì)列咏雌,而是直接運(yùn)行前三個(gè)任務(wù)换团,而后面再提交任務(wù)時(shí)由于當(dāng)前沒有空閑線程所以加入任務(wù)隊(duì)列中進(jìn)行等待悉稠,此時(shí),由于我們的任務(wù)隊(duì)列實(shí)現(xiàn)是由PriorityBlockingQueue實(shí)現(xiàn)的艘包,所以進(jìn)行等待的任務(wù)會(huì)經(jīng)過(guò)優(yōu)先級(jí)判斷的猛,優(yōu)先級(jí)高的放在隊(duì)列前面先處理。從效果圖中也可以看到后面的任務(wù)是先執(zhí)行優(yōu)先級(jí)高的任務(wù)想虎,然后依次遞減卦尊。
優(yōu)先級(jí)線程池的優(yōu)點(diǎn)
從上面我們可以得知,創(chuàng)建一個(gè)優(yōu)先級(jí)線程池非常有用舌厨,它可以在線程池中線程數(shù)量不足或系統(tǒng)資源緊張時(shí)岂却,優(yōu)先處理我們想要先處理的任務(wù),而優(yōu)先級(jí)低的則放到后面再處理裙椭,這極大改善了系統(tǒng)默認(rèn)線程池以FIFO方式處理任務(wù)的不靈活
擴(kuò)展線程池ThreadPoolExecutor
除了內(nèi)置的功能外躏哩,ThreadPoolExecutor也向外提供了三個(gè)接口供我們自己擴(kuò)展?jié)M足我們需求的線程池,這三個(gè)接口分別是:
beforeExecute() – 任務(wù)執(zhí)行前執(zhí)行的方法
afterExecute() -任務(wù)執(zhí)行結(jié)束后執(zhí)行的方法
terminated() -線程池關(guān)閉后執(zhí)行的方法
這三個(gè)方法在ThreadPoolExecutor內(nèi)部都沒有實(shí)現(xiàn)
前面兩個(gè)方法我們可以在ThreadPoolExecutor內(nèi)部的runWorker()方法中找到揉燃,而runWorker()是ThreadPoolExecutor的內(nèi)部類Worker實(shí)現(xiàn)的方法扫尺,Worker它實(shí)現(xiàn)了Runnable接口,也正是線程池內(nèi)處理任務(wù)的工作線程你雌,而Worker.runWorker()方法則是處理我們所提交的任務(wù)的方法器联,它會(huì)同時(shí)被多個(gè)線程訪問(wèn)二汛,所以我們看runWorker()方法的實(shí)現(xiàn),由于涉及到多個(gè)線程的異步調(diào)用拨拓,必然是需要使用鎖來(lái)處理肴颊,而這里使用的是Lock來(lái)實(shí)現(xiàn)的,我們來(lái)看看runWorker()方法內(nèi)主要實(shí)現(xiàn):
可以看到在task.run()之前和之后分別調(diào)用了beforeExecute和afterExecute方法渣磷,并傳入了我們的任務(wù)Runnable對(duì)象
而terminated()則是在關(guān)閉線程池的方法中調(diào)用婿着,而關(guān)閉線程池有兩個(gè)方法,我貼其中一個(gè):
所以醋界,我們要擴(kuò)展線程池竟宋,只需要重寫這三個(gè)方法,并實(shí)現(xiàn)我們自己的功能即可形纺,這三個(gè)方法分別都會(huì)在任務(wù)執(zhí)行前調(diào)用丘侠、任務(wù)執(zhí)行完成后調(diào)用、線程池關(guān)閉后調(diào)用逐样。
這里我驗(yàn)證一下蜗字,繼承自ThreadPoolExecutor 并實(shí)現(xiàn)那三個(gè)方法:
public class MyThreadPoolExecutor extends ThreadPoolExecutor {
public MyThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue workQueue) {
super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
}
@Override
protected void beforeExecute(Thread t, Runnable r) {
super.beforeExecute(t, r);
String threadName = t.getName();
Log.v("zxy", "線程:" + threadName + "準(zhǔn)備執(zhí)行任務(wù)!");
}
@Override
protected void afterExecute(Runnable r, Throwable t) {
super.afterExecute(r, t);
String threadName = Thread.currentThread().getName();
Log.v("zxy", "線程:" + threadName + "任務(wù)執(zhí)行結(jié)束脂新!");
}
@Override
protected void terminated() {
super.terminated();
Log.v("zxy", "線程池結(jié)束挪捕!");
}
}
而運(yùn)行后的結(jié)果則是,這正符合剛剛說(shuō)的
11-17 05:47:51.184 1602-1619/? V/zxy: 線程:pool-6-thread-1準(zhǔn)備執(zhí)行任務(wù)争便!
11-17 05:47:51.184 1602-1619/? V/zxy: 線程:pool-6-thread-1正在執(zhí)行任務(wù)级零!
11-17 05:47:53.184 1602-1619/? V/zxy: 線程:pool-6-thread-1任務(wù)執(zhí)行結(jié)束!
11-17 05:47:58.896 1602-1619/? V/zxy: 線程池結(jié)束滞乙!
所以奏纪,在上面我們的優(yōu)先級(jí)線程池的代碼上,我們?cè)贁U(kuò)展一個(gè)具有暫停功能的優(yōu)先級(jí)線程池酷宵,代碼如下:
具有暫時(shí)功能的線程池:
public class PausableThreadPoolExecutor extends ThreadPoolExecutor {
private boolean isPaused;
private ReentrantLock pauseLock = new ReentrantLock();
private Condition unpaused = pauseLock.newCondition();
public PausableThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue workQueue) {
super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
}
@Override
protected void beforeExecute(Thread t, Runnable r) {
super.beforeExecute(t, r);
pauseLock.lock();
try {
while (isPaused) unpaused.await();
} catch (InterruptedException ie) {
t.interrupt();
} finally {
pauseLock.unlock();
}
}
public void pause() {
pauseLock.lock();
try {
isPaused = true;
} finally {
pauseLock.unlock();
}
}
public void resume() {
pauseLock.lock();
try {
isPaused = false;
unpaused.signalAll();
} finally {
pauseLock.unlock();
}
}
}
然后結(jié)合上面的優(yōu)先級(jí)線程池的實(shí)現(xiàn)亥贸,創(chuàng)建具有暫停功能的優(yōu)先級(jí)線程池:
PausableThreadPoolExecutor pausableThreadPoolExecutor = new PausableThreadPoolExecutor(1, 1, 0L, TimeUnit.SECONDS, new PriorityBlockingQueue());
for (int i = 1; i 100; i++) {
final int priority = i;
pausableThreadPoolExecutor.execute(new PriorityRunnable(priority) {
@Override
public void doSth() {
runOnUiThread(new Runnable() {
@Override
public void run() {
textView.setText(priority + "");
}
});
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
這里我為了演示效果,把這個(gè)線程池設(shè)為只有一個(gè)線程浇垦,然后直接在TextView中顯示當(dāng)前執(zhí)行的任務(wù)的優(yōu)先級(jí)炕置,然后設(shè)置個(gè)開關(guān),控制線程池的暫停與開始:
if (isPause) {
pausableThreadPoolExecutor.resume();
isPause = false;
} else {
pausableThreadPoolExecutor.pause();
isPause = true;
}
優(yōu)化線程池ThreadPoolExecutor
雖說(shuō)線程池極大改善了系統(tǒng)的性能男韧,不過(guò)創(chuàng)建線程池也是需要資源的朴摊,所以線程池內(nèi)線程數(shù)量的大小也會(huì)影響系統(tǒng)的性能,大了反而浪費(fèi)資源此虑,小了反而影響系統(tǒng)的吞吐量甚纲,所以我們創(chuàng)建線程池需要把握一個(gè)度才能合理的發(fā)揮它的優(yōu)點(diǎn),通常來(lái)說(shuō)我們要考慮的因素有CPU的數(shù)量朦前、內(nèi)存的大小介杆、并發(fā)請(qǐng)求的數(shù)量等因素鹃操,按需調(diào)整。
通常核心線程數(shù)可以設(shè)為CPU數(shù)量+1春哨,而最大線程數(shù)可以設(shè)為CPU的數(shù)量*2+1荆隘。
獲取CPU數(shù)量的方法為:
1
Runtime.getRuntime().availableProcessors();
shutdown()和shutdownNow()的區(qū)別
關(guān)于線程池的停止,ExecutorService為我們提供了兩個(gè)方法:shutdown和shutdownNow赴背,這兩個(gè)方法各有不同椰拒,可以根據(jù)實(shí)際需求方便的運(yùn)用,如下:
1凰荚、shutdown()方法在終止前允許執(zhí)行以前提交的任務(wù)燃观。
2、shutdownNow()方法則是阻止正在任務(wù)隊(duì)列中等待任務(wù)的啟動(dòng)并試圖停止當(dāng)前正在執(zhí)行的任務(wù)便瑟。
關(guān)于AsyncTask的實(shí)現(xiàn)
大家都知道AsyncTask內(nèi)部實(shí)現(xiàn)其實(shí)就是Thread+Handler缆毁。其中Handler是為了處理線程之間的通信,而這個(gè)Thread到底是指什么呢胳徽?通過(guò)AsyncTask源碼可以得知积锅,其實(shí)這個(gè)Thread是線程池,AsyncTask內(nèi)部實(shí)現(xiàn)了兩個(gè)線程池养盗,分別是:串行線程池和固定線程數(shù)量的線程池。而這個(gè)固定線程數(shù)量則是通過(guò)CPU的數(shù)量決定的适篙。
在默認(rèn)情況下往核,我們大都通過(guò)AsyncTask::execute()來(lái)執(zhí)行任務(wù)的,
嚷节,而execute()內(nèi)部則是調(diào)用executeOnExecutor(sDefaultExecutor, params)方法執(zhí)行的聂儒,第一個(gè)參數(shù)就是指定處理該任務(wù)的線程池,而默認(rèn)情況下AsyncTask是傳入串行線程池(在這里不講版本的變化)硫痰,也就是任務(wù)只能單個(gè)的按順序執(zhí)行衩婚,而我們要是想讓AsyncTask并行的處理任務(wù),大家都知道調(diào)用AsyncTask::executeOnExecutor(sDefaultExecutor, params)方法傳入這個(gè)參數(shù)即可:AsyncTask.THREAD_POOL_EXECUTOR效斑。
而這個(gè)參數(shù)的意義在于為任務(wù)指定了一個(gè)固定線程數(shù)量的線程池去處理非春,從而達(dá)到了并行處理的功能,我們可以在源碼中看到AsyncTask.THREAD_POOL_EXECUTOR這個(gè)參數(shù)就是一個(gè)固定線程數(shù)量的線程池:
public static final Executor THREAD_POOL_EXECUTOR
= new ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE,
TimeUnit.SECONDS, sPoolWorkQueue, sThreadFactory);