About ExecutorService(1),F(xiàn)uture&FutureTask
About ExecutorService(2)继准,自定義線程池
About ExecutorService(3)枉证,我所認識的AsyncTask
About ExecutorService(4),AsyncTask番外篇
琢磨了一下移必,還是把這篇提前了室谚,本片篇幅可能會有些長,甚至冗余,請各位看官原諒我這拙劣的寫作能力秒赤。
本篇的重點是猪瞬,簡單線程池的實現(xiàn),Executor框架入篮,優(yōu)化后的自定義線程池陈瘦。
Android主線程不是線程安全的,不能阻塞太久潮售,否則會報ANR異常痊项,于是,我們經(jīng)常這樣做來處理耗時操作酥诽,查詢數(shù)據(jù)线婚,甚至聯(lián)網(wǎng)請求。
new Thread(new Runnable() {
@Override
public void run() {
/*處理耗時操作*/
}
}).start();
這段看似常用盆均,而且大家都在用,其實蘊藏著很多學問漱逸。那么我就為大家解釋一下泪姨。
這段代碼首先創(chuàng)建了一個線程,并在run( )
方法結(jié)束后饰抒,系統(tǒng)自動回收該線程肮砾,可以說在簡單的應用中,沒什么問題袋坑,但是如果放到復雜的生產(chǎn)環(huán)境中仗处,系統(tǒng)由于真實環(huán)境的需要,可能會開啟很多線程來做支撐枣宫。而當線程數(shù)量過大時婆誓,反而會消耗CUP和內(nèi)存資源。
與進程相比也颤,線程算的上是一個輕量級的工具洋幻,但是!3崛ⅰ文留!其創(chuàng)建和關閉都需要花費時間和資源,頻繁的創(chuàng)建子線程竭沫,很可能出現(xiàn)造成創(chuàng)建和銷毀線程所花的時間要比業(yè)務邏輯的工作時間還要長的悲劇燥翅,這樣就得不償失了。
Android的垃圾回收機制是基于虛擬機的蜕提,雖智能森书,但不夠完美,子線程頻繁創(chuàng)建和銷長毀,依然會搶奪資源拄氯,不僅給GC帶來壓力躲查,嚴重的時候甚至會報OOM異常。
所以译柏,應該對子線程的使用掌握一個度镣煮,接下來,duang鄙麦,duang典唇,duang,給大家?guī)砭€程池的簡單實現(xiàn)胯府。
線程池的基本功能就是進行線程的復用介衔,由于篇幅原因,我把它提前發(fā)布出來并附上鏈接骂因。
通過上面鏈接的小例子炎咖,是不是對線程池的構造有了一個初步的了解。接下來為大家介紹一下系統(tǒng)自帶的Executor框架寒波,為開發(fā)者們自定義線程池帶來了極大的方便乘盼。
可以說Executors是一個工廠類,里面有許多靜態(tài)方法俄烁,供開發(fā)者調(diào)用绸栅。
/*該方法返回一個固定線程數(shù)量的線程池,該線程池池中的線程數(shù)量始終不變页屠。當有一個新的任務提交時粹胯,線程池中若有空閑線程,則立即執(zhí)行辰企。
* 若沒有风纠,則新的任務會被暫存在一個任務隊列中,待有線程空閑時蟆豫,便處理在任務隊列中的任務
* 默認等待隊列長度為Integer.MAX_VALUE*/
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(1);
/*該方法返回一個只有一個線程的線程池议忽。
* 若多余一個任務被提交到線程池,任務會被保存在一個任務隊列中十减,等待線程空閑栈幸,按先入先出順序執(zhí)行隊列中的任務
* 默認等待隊列長度為Integer.MAX_VALUE*/
ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
/*該方法返回一個可根據(jù)實際情況調(diào)整線程數(shù)量的線程池。
* 線程池的線程數(shù)量不確定帮辟,但若有空閑線程可以復用速址,則會優(yōu)先使用可復用的線程。
* 若所有線程均在工作由驹,又有新任務的提交芍锚,則會創(chuàng)建新的線程處理任務昔园。
* 所有線程在當前任務執(zhí)行完畢后,將返回線程池進行復用*/
ExecutorService newCachedThreadPool = Executors.newCachedThreadPool();
/*該方法返回一個ScheduledExecutorService對象并炮,線程池大小為1默刚。
* ScheduledExecutorService接口在ExecutorService接口之上擴展了在給定時間內(nèi)執(zhí)行某任務的功能,
* 如在某個固定的延時之后執(zhí)行逃魄,或者周期性執(zhí)行某個任務*/
ExecutorService newSingleThreadScheduledExecutor = Executors.newSingleThreadScheduledExecutor();
/*該方法也返回一個ScheduledExecutorService對象荤西,但該線程池可以指定線程數(shù)量*/
ExecutorService newScheduledThreadPool = Executors.newScheduledThreadPool(1);
如果童鞋們認為,Executors工廠類提供的自定義線程池伍俘,還不夠用的話邪锌,也可以自定義線程池。通過ThreadPoolExecutor的構造函數(shù)自定義需要的線程池癌瘾。
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), defaultHandler);
}
corePoolSize:當提交一個任務到線程池時觅丰,線程池會創(chuàng)建一個線程來執(zhí)行任務,即使其他空閑的基本線程能夠執(zhí)行新任務也會創(chuàng)建線程妨退,等到需要執(zhí)行的任務數(shù)大于線程池基本大小時就不再創(chuàng)建妇萄。如果調(diào)用了線程池的prestartAllCoreThreads方法,線程池會提前創(chuàng)建并啟動所有基本線程咬荷。
maximumPoolSize:線程池允許創(chuàng)建的最大線程數(shù)嚣伐。如果隊列滿了,并且已創(chuàng)建的線程數(shù)小于最大線程數(shù)萍丐,則線程池會再創(chuàng)建新的線程執(zhí)行任務。值得注意的是如果使用了無界的任務隊列這個參數(shù)就沒什么效果放典。
keepAliveTime:當線程池線程數(shù)量超過corePoolSize時逝变,多余的空余線程存活時間,即奋构,超過corePoolSize的空閑線程壳影,在多長時間內(nèi)被銷毀。如果任務很多弥臼,并且每個任務執(zhí)行的時間比較短宴咧,可以調(diào)大這個時間,提高線程的利用率径缅。
unit:keepAliveTime的單位掺栅。
workQueue:任務隊列,被提交但尚未被執(zhí)行任務的任務纳猪。有以下幾種隊列模式:
ArrayBlockingQueue:是一個基于數(shù)組結(jié)構的有界阻塞隊列氧卧,此隊列按 FIFO(先進先出)原則對元素進行排序。
LinkedBlockingQueue:一個基于鏈表結(jié)構的阻塞隊列氏堤,此隊列按FIFO (先進先出) 排序元素沙绝,吞吐量通常要高于ArrayBlockingQueue。靜態(tài)工廠方法Executors.newFixedThreadPool()使用了這個隊列。
SynchronousQueue:一個不存儲元素的阻塞隊列闪檬。每個插入操作必須等到另一個線程調(diào)用移除操作星著,否則插入操作一直處于阻塞狀態(tài),吞吐量通常要高于LinkedBlockingQueue粗悯,靜態(tài)工廠方法Executors.newCachedThreadPool使用了這個隊列虚循。
PriorityBlockingQueue:一個具有優(yōu)先級得無限阻塞隊列。
threadFactory:線程工廠为黎,用于創(chuàng)建線程邮丰,一般用默認的即可。也可以通過線程工廠給每個創(chuàng)建出來的線程設置更有意義的名字铭乾,Debug和定位問題時非常有幫助剪廉。
handler:拒絕策略,當隊列和線程池都滿了炕檩,說明線程池處于飽和狀態(tài)斗蒋,那么必須采取一種策略處理提交的新任務。這個策略默認情況下是AbortPolicy笛质,表示無法處理新任務時拋出異常泉沾。系統(tǒng)內(nèi)置的策略模式如下:
AbortPolicy:直接拋出異常,阻止系統(tǒng)正常工作妇押。
CallerRunsPolicy:只要線程未關閉跷究,該策略直接在調(diào)用者線程中,運行當前被丟棄的任務敲霍。
DiscardOldestPolicy:該策略將丟棄最老的一個請求俊马,也就是即將被執(zhí)行的一個人任務,并嘗試再次提交當前任務肩杈。
DiscardPolicy:該策略默默的丟棄無法處理對
當然也可以根據(jù)應用場景需要來實現(xiàn)RejectedExecutionHandler接口自定義策略柴我。
好像好久沒說到AT了,那么接下來我們根據(jù)源碼和畫圖的方式分析一下AT中到底構造了一個如何的線程池
private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
private static final int CORE_POOL_SIZE = CPU_COUNT + 1;
private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1;
private static final int KEEP_ALIVE = 1;
private static final ThreadFactory sThreadFactory = new ThreadFactory() {
private final AtomicInteger mCount = new AtomicInteger(1);
public Thread newThread(Runnable r) {
return new Thread(r, "AsyncTask #" + mCount.getAndIncrement());
}
};
private static final BlockingQueue<Runnable> sPoolWorkQueue =
new LinkedBlockingQueue<Runnable>(128);
/**
* An {@link Executor} that can be used to execute tasks in parallel.
*/
public static final Executor THREAD_POOL_EXECUTOR
= new ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE,
TimeUnit.SECONDS, sPoolWorkQueue, sThreadFactory);
為了保持處理器達到期望的使用率扩然,最優(yōu)線程池大小艘儒,終于在一本叫做《Java并發(fā)編程實踐》的書中找到了估算線程池大小的經(jīng)驗值:Nthread。
Nthread = Ncpu * Ucpu * (1 + W/C)
其中夫偶,Ncpu = CPU的數(shù)量界睁,Ucpu = 目標CPU的使用量,0 <= Ucpu <=1兵拢,W/C = 等待時間與計算時間的比率晕窑。
由于各種八核手機出現(xiàn),以及硬件的提升卵佛,這個公式貌似也不常用了杨赤。大家了解即可敞斋。
接下來介紹AT中線程池的工作原理
1,假如我手中用了雙核手機(兩個CPU)疾牲,初始狀態(tài)下線程池的工作線程為0植捎,當?shù)谝粋€任務進來時,會創(chuàng)建工作線程阳柔。則根據(jù)AT中計算CPU_COUNT = 2焰枢,CORE_POOL_SIZE = 3 ,紅色代表新任務舌剂,當線程池中的工作線程池數(shù)量已經(jīng)達到了CORE_POOL_SIZE济锄,下個任務,如果工作線程沒有空閑的話霍转,必將進入緩沖隊列中荐绝,進行排隊。
2避消,線程池中沒有空閑線程低滩,新的任務要進入緩沖隊列進行排隊。
3岩喷,緩沖隊列也滿了恕沫,線程池中的工作線程依然沒有空閑。
通過AT中的計算纱意,MAXIMUM_POOL_SIZE = 5婶溯。
當線程池工作線程達到CORE_POOL_SIZE并且沒有空閑,緩沖隊列任務數(shù)量達到AT中設定的128偷霉,
此時新的任務進入線程池之前爬虱,如果線程池中工作線程數(shù)量小于MAXIMUM_POOL_SIZE,則創(chuàng)建新的工作線程執(zhí)行任務腾它。如下圖,線程池創(chuàng)建第4個工作線程死讹,執(zhí)行第129個任務瞒滴。
4.線程池工作線程數(shù)量等于MAXIMUM_POOL_SIZE,緩沖隊列數(shù)量等于最大workQuene最大值赞警,并且沒有工作線程處于空閑妓忍,第131任務進入線程池的時候,拒絕策略handler發(fā)揮作用愧旦,AT中默認的拒絕策略為拋出RuntimeException異常世剖。(RuntimeException和Error同屬于UncaughtException,可以在Application中進行捕獲和處理)
由上述步驟笤虫,不難發(fā)現(xiàn)workQueue應該避免無邊界隊列旁瘫,盡量使用帶邊界的祖凫,如ArrayBlockingQueue和固定capacity容量的LinkedBlockingQueue。否則線程池的緩沖隊列將是一個無邊界的隊列酬凳,任務過多而不能執(zhí)行拒絕策略的情況下惠况,可能會撐滿內(nèi)存,導致整個系統(tǒng)不可用宁仔,造成不可預估的后果稠屠。
片尾Tip:
最近有群里的朋友抱怨,Android studio 編譯速度太慢翎苫,太卡了权埠,交給大家一個小技巧,我的電腦還是公司的老款戴爾煎谍,至于你信不信攘蔽,反正我是信了,它確實快了粱快。