Thread
????????在Android應(yīng)用開發(fā)過程中,我們不可避免的要使用網(wǎng)絡(luò)請(qǐng)求去獲取數(shù)據(jù)宪郊,或者從數(shù)據(jù)庫(kù)峡扩、文件中讀取數(shù)據(jù)蹭越,亦或需在業(yè)務(wù)開發(fā)中進(jìn)行某些耗時(shí)操作。此時(shí)教届,就需要將這些任務(wù)放置于子線程中進(jìn)行响鹃,使用線程的方式有很多種驾霜,除了線程池(Executor)之外,你可以使用AsyncTask买置、HandlerThread粪糙、IntentService等来颤,你也可以直接實(shí)例化Thread調(diào)用其start來開啟一個(gè)子線程融求。當(dāng)然殊途同歸,最終還是由Thread來但此重任钧唐。
可以直接在主線中操作么轩触?
? ? ? ? 答案是肯定的寞酿,你也可以選擇在主線中進(jìn)行上述的除了“網(wǎng)絡(luò)請(qǐng)求”之外的耗時(shí)操作,就跟自個(gè)兒燒菜吃一個(gè)兒道理脱柱,你可以清蒸熟嫩、紅燒、煲湯褐捻、爆炒著吃,也可以百無顧慮的選擇生吃椅邓,當(dāng)然吃壞肚子(ANR)就是另一回事了柠逞。那為什么我們說“除了網(wǎng)絡(luò)請(qǐng)求之外”呢?難道網(wǎng)絡(luò)請(qǐng)求就一定不能在主線程中進(jìn)行了嘛景馁?都是一個(gè)媽生的板壮,憑啥我就不能在主線程里進(jìn)行了,莫不是我長(zhǎng)得特別丑合住,長(zhǎng)得丑我就不能在自個(gè)兒的地盤上網(wǎng)撩妹了绰精?
其實(shí),在Android4.0(API14)之前透葛,系統(tǒng)并沒有限制你在主線程中進(jìn)行網(wǎng)絡(luò)請(qǐng)求笨使,也就是說那個(gè)年代我們還能愉快的”上網(wǎng)撩妹“,當(dāng)然結(jié)果自然是撩一次被扇一次臉僚害。
????????由于網(wǎng)絡(luò)請(qǐng)求必然是個(gè)耗時(shí)操作硫椰,更何況在移動(dòng)網(wǎng)絡(luò)并沒有像有線寬帶那么穩(wěn)定的情況下(什么,你敢跟我說有線寬帶穩(wěn)定萨蚕?打PC游戲的小伙伴們心里有句MMP不知道當(dāng)不當(dāng)講)靶草。
? ? ? ? 回歸正題,我們從線程池的使用岳遥、各個(gè)參數(shù)意義奕翔、線程池的原理來逐步對(duì)其進(jìn)行介紹。
一浩蓉、線程池的基本使用及參數(shù)
? ? ? ? 之所以使用線程池派继,是為了盡可能減少資源的消耗宾袜,提高資源復(fù)用』グ“池”之所以為“池”试和,就是可以往里放東西,也可以從里取東西纫普,而這個(gè)東西就指的是“線程”阅悍。我們不可能為每個(gè)任務(wù)的啟動(dòng)都去開一個(gè)線程,因?yàn)殚_辟一個(gè)線程也是需要消耗系統(tǒng)資源昨稼,不要以為開啟子線程就不會(huì)造成界面卡頓了节视,線程開辟的過多意味著占用系統(tǒng)資源越多,這也是常見的卡頓原因假栓。因此寻行,我們需要線程池來充當(dāng)對(duì)線程的管理角色,用于減少不必要線程的創(chuàng)建匾荆。
? ? ? ? 那么拌蜘,線程池是怎么來管理如此多的子線程的呢?
? ? ? ? 首先牙丽,我們來看看線程池的類繼承結(jié)構(gòu):
1. Executor定義了execute(Runnable command)接口简卧,用于提交一個(gè)任務(wù),該任務(wù)可以在新線程/線程池/任務(wù)提交線程執(zhí)行
2. ExecutorService定義了submit烤芦、shutdown等多個(gè)接口举娩,用于控制線程的提交、結(jié)束构罗、進(jìn)度的跟蹤
3.?AbstractExecutorService實(shí)現(xiàn)了ExecutorService接口铜涉,使用Future、Callable進(jìn)行對(duì)線程的具體控制與結(jié)果回調(diào)遂唧,但他只是一個(gè)抽象類
4.?ThreadPoolExecutor繼承自AbstractExecutorService芙代,也即我們真正接觸的線程池類,可以看看源碼注釋:
譯文大概是:ThreadPoolExecutor是一個(gè)用于執(zhí)行每個(gè)提交的任務(wù)的具體實(shí)例化的ExecutorService蠢箩,也就是線程池的真正實(shí)現(xiàn)链蕊,一般通過線程池工廠方法創(chuàng)建配置。
????????在實(shí)際使用的過程中谬泌,我們常見的用Executors來創(chuàng)建所需的線程池滔韵,Executors可以理解成一個(gè)線程池工廠類,用于提供多個(gè)常見線程池的構(gòu)建掌实。在沒有特殊需要的情況下陪蜻,我們確實(shí)可以直接使用Executors提供的4種常見線程池:
1.FixedThreadPool:作為擁有固定的線程數(shù)量(核心線程)的線程池,無非核心線程贱鼻,它的核心線程無論是在工作狀態(tài)還是空閑狀態(tài)宴卖,都會(huì)一直留存滋将,不會(huì)被回收,可以將其認(rèn)為是以空間來?yè)Q取時(shí)間的做法症昏。
對(duì)于新增線程超出了所設(shè)定的核心線程數(shù)時(shí)随闽,新增的線程將會(huì)被保存在阻塞隊(duì)列LinkedBlockingQueue中等待,待有線程空閑時(shí)才能夠取出來進(jìn)入工作狀態(tài)肝谭,LinkedBlockingQueue可詳見——細(xì)談Java并發(fā)】談?wù)凩inkedBlockingQueue
2.SingleThreadExecutor:顧名思義掘宪,它內(nèi)部只包含一個(gè)核心線程,且沒有非核心線程攘烛,就跟我們常見的AsyncTask調(diào)用execute一般魏滚,是一個(gè)單線程的運(yùn)行模式,這樣的好處是無需處理線程同步的問題坟漱,當(dāng)然缺陷也是一目了然鼠次,只能是單線程阻塞進(jìn)行任務(wù)的處理。
3.CachedThreadPool:沒有核心線程芋齿,只有非核心線程腥寇,并且其線程數(shù)量并不固定,最大值為Integer.MAX_VALUE觅捆,且每個(gè)非核心線程都有其超時(shí)時(shí)長(zhǎng)花颗,超過這個(gè)時(shí)間不處于活動(dòng)狀態(tài)便會(huì)被銷毀,當(dāng)有新的任務(wù)需要提交時(shí)惠拭,如果當(dāng)前線程池中的線程都處于活動(dòng)狀態(tài),那么就會(huì)新建一個(gè)線程用于執(zhí)行新的任務(wù)庸论,否則如果有處于超時(shí)時(shí)間中的空閑線程职辅,就會(huì)利用該空閑線程。
可以看到聂示,CachedThreadPool構(gòu)造中的隊(duì)列與前兩者不同域携,使用的是SynchronousQueue,這是一種無法存儲(chǔ)元素的阻塞隊(duì)列鱼喉,可以參考JUC源碼分析-集合篇(八):SynchronousQueue秀鞭。從CachedThreadPool的這種特性中可以得出,它適宜進(jìn)行大量線程的執(zhí)行扛禽,且這些線程不會(huì)導(dǎo)致過長(zhǎng)的耗時(shí)時(shí)間(如果耗時(shí)時(shí)間都非常長(zhǎng)锋边,那么結(jié)局也就可以預(yù)見了,會(huì)導(dǎo)致系統(tǒng)資源的大量消耗并會(huì)導(dǎo)致OOM)编曼。
4.ScheduledThreadPool:它與CachedThreadPool有所不同的是豆巨,具有一定數(shù)量的核心線程數(shù),且雖然其非核心線程數(shù)也是Integer.MAX_VALUE掐场,但是不像CachedThreadPool擁有超時(shí)時(shí)間往扔,其非核心線程一旦有空閑贩猎,就會(huì)立即回收
使用了DelayedWorkQueue,可參考Java優(yōu)先級(jí)隊(duì)列DelayedWorkQueue原理分析萍膛,優(yōu)先級(jí)隊(duì)列來控制線程任務(wù)的出入吭服,通過任務(wù)延時(shí)調(diào)整優(yōu)先級(jí)來控制其出入順序,因此適用于執(zhí)行一些定時(shí)性的周期性任務(wù)蝗罗。
? ? ? ? 以上為內(nèi)置的4種不同類型的線程池構(gòu)造方式艇棕,已可滿足一部分使用場(chǎng)景中所需。接下來我們介紹線程池各個(gè)構(gòu)造參數(shù)的意義绿饵,助于自定義配置線程池欠肾。我們先來看看ThreadPoolExecutor的構(gòu)造參數(shù):
1.corePoolSize:顧名思義是核心線程的數(shù)量,在allowCoreThreadTimeOut為false的情況下(默認(rèn)false)拟赊,核心線程會(huì)一直存活刺桃,即使處于空閑狀態(tài)。當(dāng)allowCoreThreadTimeOut設(shè)置為true時(shí)吸祟,核心線程也跟非核心線程一樣瑟慈,將會(huì)有超時(shí)時(shí)間,超過超時(shí)時(shí)間屋匕,空閑的核心線程也會(huì)被回收葛碧。
2.maximumPoolSize:核心線程+非核心線程的數(shù)量,也即線程池中的最大線程數(shù)量过吻,若超過該數(shù)量进泼,新提交的線程會(huì)被阻塞。
3.keepAliveTime:一般而言指的是非核心線程的超時(shí)時(shí)間纤虽,當(dāng)線程處于閑置狀態(tài)時(shí)超出該時(shí)間乳绕,將會(huì)被回收,如上提到的若將allowCoreThreadTimeOut設(shè)為true逼纸,也可同時(shí)作用于核心線程洋措。
4.TimeUnit:即keepAliveTime的單位,有MILLISECONDS——毫秒杰刽;SECONDS——秒菠发;MINUTES:——HOURS——時(shí);DAYS——天贺嫂。
5.workQueue:任務(wù)隊(duì)列滓鸠,常見的有LinkedBlockingQueue、SynchronousQueue第喳、DelayedWorkQueue等哥力。
6.threadFactory:擔(dān)任線程工廠的角色,默認(rèn)實(shí)現(xiàn)為DefaultThreadFactory,如下:
可以看到吩跋,DefaultThreadFactory通過呢newThread來創(chuàng)建一個(gè)線程寞射,并將線程優(yōu)先級(jí)設(shè)置為NORM_PRIORITY,使用AtomicInteger原子類通過CAS來保障線程數(shù)安全锌钮。
7.RejectedExecutionHandler:字面意思為線程池的拒絕策略桥温,當(dāng)線程池的線程數(shù)達(dá)到限制時(shí),且阻塞隊(duì)列也滿時(shí)梁丘,需要再提交新的任務(wù)侵浸,那么我們就需要使用到RejectedExecutionHandler來對(duì)這些不能處理的任務(wù)的處理策略。常用的策略為:
? ? (1)AbortPolicy:該策略為默認(rèn)策略氛谜,當(dāng)隊(duì)列已滿無法添加新的任務(wù)時(shí)掏觉,會(huì)拋掉該任務(wù),并且拋出RejectedExecutionException異常值漫。
????(2)DiscardPolicy:該策略相比AbortPolicy 不同之處在于其不會(huì)拋任何異常澳腹,直接丟棄。
????(3)DiscardOldestPolicy:該策略為“喜新厭舊”策略杨何,先進(jìn)先棄酱塔,長(zhǎng)江后浪推前浪,把前浪排死在沙灘上危虱,形象的比擬了我們碼農(nóng)這個(gè)職業(yè)羊娃。
? ? (4)DiscardOldestPolicy:該策略比較友善,線程池不執(zhí)行埃跷,那么提交該線程的線程就負(fù)責(zé)去執(zhí)行(一般場(chǎng)景下我們都在主線程提交)
因此蕊玷,若是個(gè)網(wǎng)絡(luò)請(qǐng)求或者耗時(shí)任務(wù)的話那么就GG,頗有種項(xiàng)羽烏江自刎的壯烈感弥雹。
當(dāng)然集畅,除此上述4種內(nèi)置策略之外,我們也可以選擇實(shí)現(xiàn)RejectedExecutionHandler接口去實(shí)現(xiàn)我們自定義的策略缅糟。
二、線程池原理
? ? ? ? 首先祷愉,我們從線程池的啟動(dòng)入口execute(submit內(nèi)部也是調(diào)用execute)開始:
? ? ? ? 若提交的任務(wù)Runnable不為空窗宦,則會(huì)進(jìn)入workerCount當(dāng)前正在工作中的線程數(shù)的判定,ctl即AtomicInteger二鳄,用于保證工作線程數(shù)的原子性赴涵,若當(dāng)前工作線程數(shù)小于設(shè)定的核心線程數(shù)corePoolSize,則會(huì)調(diào)用addWorker提交創(chuàng)建新的核心線程订讼,我們來看看addWorker中做了什么:
未完待續(xù)...