前言
線程池是Java中的一個(gè)重要概念,從Android上來說,當(dāng)我們跟服務(wù)端進(jìn)行數(shù)據(jù)交互的時(shí)候我們都知道主線程不能進(jìn)行聯(lián)網(wǎng)操作以及耗時(shí)操作,主線程進(jìn)行聯(lián)網(wǎng)操作在3.0之后會(huì)報(bào)一個(gè)NewWorkOnMainTHreadException的異常,而在主線程進(jìn)行耗時(shí)操作則會(huì)引起ANR(Application Not Responding),但是我們經(jīng)常要跟服務(wù)端進(jìn)行交互,下載和上傳數(shù)據(jù)等,這也就是進(jìn)行聯(lián)網(wǎng)操作,在初學(xué)時(shí)我們都是通過new Thread來開啟一個(gè)線程進(jìn)行聯(lián)網(wǎng)操作,但是跟服務(wù)端交互多了,如果還是使用new Thread()來開啟子線程,在一個(gè)應(yīng)用中我們頻繁的去通過這個(gè)方法去開啟線程,這對(duì)性能來說是很大的浪費(fèi),頻繁的開啟銷毀線程對(duì)內(nèi)存的消耗是很大的,而頻繁的開啟線程也讓整個(gè)應(yīng)用的線程管理顯得很混亂,這是不可取的,這時(shí)候使用線程池就可以解決這些問題了,這篇文章我嘗試將線程池概念和應(yīng)用說清楚,然后封裝一個(gè)自定義的線程池策略以后根據(jù)業(yè)務(wù)需求稍微更改下相關(guān)的參數(shù)即可。
線程池好處
線程池可以解決兩個(gè)不同問題:由于減少了每個(gè)任務(wù)調(diào)用的開銷,它們通常可以在執(zhí)行大量異步任務(wù)時(shí)提供增強(qiáng)的性能,并且還可以提供綁定和管理資源(包括執(zhí)行任務(wù)集時(shí)使用的線程)的方法。每個(gè) ThreadPoolExecutor 還維護(hù)著一些基本的統(tǒng)計(jì)數(shù)據(jù),如完成的任務(wù)數(shù)。 - - - - -Doug Lea(線程池設(shè)計(jì)者)
從前文也可以得出線程池有下面幾個(gè)好處
(1)自定義線程池策略可以重用線程池中的線程,避免應(yīng)用中頻繁的創(chuàng)建和銷毀線程所造成的內(nèi)存消耗以及性能上不必要的開銷;
(2)通過控制線程池的最大線程數(shù)能有效控制線程池的最大并發(fā)數(shù),避免大量的線程同一時(shí)間并發(fā)搶占系統(tǒng)資源而造成的阻塞和界面卡死;
(3)可以自己管理線程,定時(shí)執(zhí)行,間隔循環(huán)執(zhí)行,線程關(guān)閉等,通過對(duì)線程的管理可以避免濫用多線程造成的問題,比如內(nèi)存泄露、界面卡死、CPU阻塞等
ThreadPoolExecutor構(gòu)造方法解釋
線程池有著很深的體系,如果每個(gè)類每個(gè)接口都細(xì)說顯得不現(xiàn)實(shí),所以這里重講它的實(shí)現(xiàn)類,Executor是線程池的頂級(jí)接口,而ThreadPoolExecutor是它的實(shí)現(xiàn)類,這個(gè)類提供了一系列方法來配置我們自己的線程池,它也封裝了一些典型的線程池策略供我們使用.
ThreadPoolExecutor繼承自AbstractExecutorService,而AbstractExecutorService實(shí)現(xiàn)了ExecutorService接口,ExecutorService接口繼承了Executor,通過改變ThreadPoolExecutor構(gòu)造方法里的參數(shù)我們可以自己定義線程池的屬性,它一共有四個(gè)構(gòu)造方法,比較常用的是下面這個(gè)方法
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory)
但是為了對(duì)這個(gè)類進(jìn)行說明我選擇的是參數(shù)最多的方法,因?yàn)椴煌瑯?gòu)造方法最終回調(diào)的都是這個(gè)方法,只是設(shè)計(jì)者幫我做了一些默認(rèn)的操作所以不用自己設(shè)置。
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
corePoolSize
線程池的保存的線程數(shù),它包括空閑線程,上面是api中的解釋,其實(shí)詳細(xì)了說這是線程池中的核心線程數(shù),在默認(rèn)情況下,即使線程是空閑狀態(tài),這個(gè)核心線程也會(huì)在線程池中存在。但是設(shè)計(jì)進(jìn)行了一些邏輯處理
/**
* Returns true if this pool allows core threads to time out and
* terminate if no tasks arrive within the keepAlive time, being
* replaced if needed when new tasks arrive. When true, the same
* keep-alive policy applying to non-core threads applies also to
* core threads. When false (the default), core threads are never
* terminated due to lack of incoming tasks.
*
* @return {@code true} if core threads are allowed to time out,
* else {@code false}
*
* @since 1.6
*/
public boolean allowsCoreThreadTimeOut() {
return allowCoreThreadTimeOut;
}
/**
* If false (default), core threads stay alive even when idle.
* If true, core threads use keepAliveTime to time out waiting
* for work.
*/
private volatile boolean allowCoreThreadTimeOut;
從注釋我們可以看出如果把a(bǔ)llowCoreThreadTimeOut的屬性值設(shè)置為true(默認(rèn)是false)那么現(xiàn)在空閑的核心線程在等待新任務(wù)時(shí)會(huì)使用一個(gè)超時(shí)策略,這個(gè)時(shí)間間隔由keepAliveTime設(shè)置,當(dāng)空閑線程等待的時(shí)間超過設(shè)置時(shí)間時(shí)核心線程將被終止。
maximumPoolSize
線程池允許容納的最大線程數(shù),當(dāng)活動(dòng)的線程達(dá)到這個(gè)最大數(shù)時(shí)如果后續(xù)還有任務(wù),那新任務(wù)將會(huì)被阻塞(通過參數(shù)可以設(shè)置阻塞時(shí)的不同處理策略)。
keepAliveTime
非核心線程閑置時(shí)的超時(shí)時(shí)長,非核心線程可以這么理解,比如核心線程數(shù)是2,最大線程數(shù)3,那么這三個(gè)里面有一個(gè)就是非核心線程了,用個(gè)比喻就是臨時(shí)工,當(dāng)核心成員干不完任務(wù)就會(huì)叫上臨時(shí)工一起,但是任務(wù)完成了臨時(shí)工什么時(shí)候辭退就是自己指定了(比喻可能不恰當(dāng),不帶任何其他的隱喻),回到文章,當(dāng)超過這個(gè)時(shí)長時(shí),非核心線程就會(huì)被回收,但是剛才介紹corePoolSize的時(shí)候也說了,如果把a(bǔ)llowCoreThreadTimeOut的屬性設(shè)置為true,keepAliveTime也可以同樣對(duì)核心線程進(jìn)行終止操作。
unit
就是keepAliveTime的時(shí)間單位,直接用TimeUnit這個(gè)枚舉就可以點(diǎn)出來,常用的有TimeUnit.MINUTS(分),TimeUnit.SECONDS(秒),TimeUnit.MILLISECONDS(毫秒).不常用的有TimeUnit.HOURS(小時(shí)),TimeUnit.DAYS(天)。
workQueue
線程池中的任務(wù)隊(duì)列(阻塞隊(duì)列),線程池的execute方法提交的Runnable對(duì)象存儲(chǔ)在這個(gè)隊(duì)列里面,BlockingQueue是線程安全的,常用的阻塞隊(duì)列有如下五個(gè)
(1).LinkedBlockingQueue
鏈表阻塞隊(duì)列,這個(gè)隊(duì)列由一個(gè)鏈表構(gòu)成,它內(nèi)部維護(hù)了一個(gè)數(shù)據(jù)緩存隊(duì)列,這個(gè)隊(duì)列按 FIFO(先進(jìn)先出)排序元素。隊(duì)列的頭部 是在隊(duì)列中時(shí)間最長的元素。隊(duì)列的尾部 是在隊(duì)列中時(shí)間最短的元素。新元素插入到隊(duì)列的尾部,并且隊(duì)列獲取操作會(huì)獲得位于隊(duì)列頭部的元素。鏈接隊(duì)列的吞吐量通常要高于基于數(shù)組的隊(duì)列,但是在大多數(shù)并發(fā)應(yīng)用程序中,其可預(yù)知的性能要低。 當(dāng)構(gòu)造這個(gè)隊(duì)列對(duì)象時(shí),如果不指定隊(duì)列容量大小,它的默認(rèn)容量大小是Integer.MAX_VALUE(無限大),這樣就是一個(gè)無界隊(duì)列,除非插入節(jié)點(diǎn)會(huì)使隊(duì)列超出容量,否則每次插入后會(huì)動(dòng)態(tài)地創(chuàng)建鏈接節(jié)點(diǎn)。
(2).ArrayBlockingQueue
數(shù)組阻塞隊(duì)列,這是一個(gè)有界阻塞隊(duì)列內(nèi)部,維護(hù)了一個(gè)定長數(shù)組用來緩存隊(duì)列中的數(shù)據(jù)對(duì)象,通過兩個(gè)整型變量來標(biāo)識(shí)隊(duì)列的頭跟尾在數(shù)組中的位置,這個(gè)隊(duì)列按 FIFO(先進(jìn)先出)原則對(duì)元素進(jìn)行排序。隊(duì)列的頭部是在隊(duì)列中存在時(shí)間最長的元素。隊(duì)列的尾部 是在隊(duì)列中存在時(shí)間最短的元素。新元素插入到隊(duì)列的尾部,隊(duì)列獲取操作則是從隊(duì)列頭部開始獲得元素。這是一個(gè)典型的“有界緩存區(qū)”,固定大小的數(shù)組在其中保持生產(chǎn)者插入的元素和使用者提取的元素。一旦創(chuàng)建了這樣的緩存區(qū),就不能再增加其容量。試圖向已滿隊(duì)列中放入元素會(huì)導(dǎo)致操作受阻塞;試圖從空隊(duì)列中提取元素將導(dǎo)致類似阻塞。
此類支持對(duì)等待的生產(chǎn)者線程和使用者線程進(jìn)行排序的可選公平策略。默認(rèn)情況下,不保證是這種排序。然而,通過將公平性 (fairness) 設(shè)置為 true 而構(gòu)造的隊(duì)列允許按照 FIFO 順序訪問線程。公平性通常會(huì)降低吞吐量,但也減少了可變性和避免了“不平衡性”。
(3).DelayQueue
延遲隊(duì)列,它也是一個(gè)無界阻塞隊(duì)列,只有在延遲期滿時(shí)才能從中提取元素。這個(gè)隊(duì)列的頭部是延遲期滿后保存時(shí)間最長的 Delayed 元素。如果延遲都還沒有期滿,則隊(duì)列沒有頭部,并且 poll 將返回 null。當(dāng)一個(gè)元素的 getDelay(TimeUnit.NANOSECONDS) 方法返回一個(gè)小于等于 0 的值時(shí),將發(fā)生到期。即使無法使用 take 或 poll 移除未到期的元素,也不會(huì)將這些元素作為正常元素對(duì)待。例如,size 方法同時(shí)返回到期和未到期元素的計(jì)數(shù)。此隊(duì)列不允許使用 null 元素。 通常用這個(gè)隊(duì)列來管理一個(gè)超時(shí)未響應(yīng)隊(duì)列。
(4).PriorityBlockingQueue
優(yōu)先阻塞隊(duì)列,它也是一個(gè)無界阻塞隊(duì)列,它使用與類 PriorityQueue 相同的順序規(guī)則,并且提供了阻塞獲取操作。雖然這個(gè)隊(duì)列邏輯上是無界的,但是資源被耗盡時(shí)試圖執(zhí)行 add 操作也將失敗(導(dǎo)致 OutOfMemoryError)。此類不允許使用null(空)元素。依賴自然順序的優(yōu)先級(jí)隊(duì)列也不允許插入不可比較的對(duì)象(這樣做會(huì)導(dǎo)致拋出 ClassCastException)。 在創(chuàng)建隊(duì)列對(duì)象時(shí)通過構(gòu)造函數(shù)中的Comparator屬性對(duì)象來決定優(yōu)先級(jí)。
(5).SynchronousQueue
同步阻塞隊(duì)列,一種無緩沖阻塞隊(duì)列,其中每個(gè)插入操作必須等待另一個(gè)線程的對(duì)應(yīng)移除操作 ,反之亦然。同步隊(duì)列沒有任何內(nèi)部容量,甚至連一個(gè)隊(duì)列的容量都沒有。不能在同步隊(duì)列上進(jìn)行 peek,因?yàn)閮H在試圖要移除元素時(shí),該元素才存在;除非另一個(gè)線程試圖移除某個(gè)元素,否則也不能(使用任何方法)插入元素;也不能迭代隊(duì)列,因?yàn)槠渲袥]有元素可用于迭代。隊(duì)列的頭 是嘗試添加到隊(duì)列中的首個(gè)已排隊(duì)插入線程的元素;如果沒有這樣的已排隊(duì)線程,則沒有可用于移除的元素并且 poll() 將會(huì)返回 null。對(duì)于其他 Collection (集合)方法(例如 contains),同步隊(duì)列作為一個(gè)空集合這個(gè)不允許null(空)元素。
同步隊(duì)列類似于 CSP 和 Ada 中使用的 rendezvous 信道。它非常適合于傳遞性設(shè)計(jì),在這種設(shè)計(jì)中,在一個(gè)線程中運(yùn)行的對(duì)象要將某些信息、事件或任務(wù)傳遞給在另一個(gè)線程中運(yùn)行的對(duì)象,它就必須與該對(duì)象同步。
對(duì)于正在等待的生產(chǎn)者和使用者線程而言,此類支持可選的公平排序策略。默認(rèn)情況下不保證這種排序。但是,使用公平設(shè)置為 true 所構(gòu)造的隊(duì)列可保證線程以 FIFO 的順序進(jìn)行訪問。
threadFactory
線程工廠,用于在線程池中創(chuàng)建新線程,線程池設(shè)計(jì)者提供了兩個(gè)(其實(shí)是一個(gè))線程池工廠給我們使用,一個(gè)是defaultThreadFactory(),另一個(gè)是privilegedThreadFactory,但是查看源碼我發(fā)現(xiàn)
/**
* Legacy security code; do not use.
*/
public static ThreadFactory privilegedThreadFactory() {
return new PrivilegedThreadFactory();
}
設(shè)計(jì)者說這是遺留的安全代碼,叫我們不要使用privilegedThreadFactory..,所以一般線程池工廠我們用的是defaultThreadFactory,用法很簡單,直接用Executors.defaultThreadFactory();就可以創(chuàng)建一個(gè)默認(rèn)線程池工廠。
handler
這個(gè)參數(shù)是一個(gè)RejectedExecutionHandler對(duì)象,它是一個(gè)接口,只有rejectedExecution這個(gè)方法,這個(gè)參數(shù)的作用是當(dāng)線程池由于任務(wù)隊(duì)列已滿或別的原因無法執(zhí)行新任務(wù)時(shí),ThreadPoolExecutor就會(huì)回調(diào)實(shí)現(xiàn)了這個(gè)接口的類來處理被拒絕任務(wù),它的使用是直接用THreadPoolExecutor.XXX,線程池設(shè)計(jì)者提供了四個(gè)處理方法:
**(1).ThreadPoolExecutor.AbortPolicy **
直接拋出RejectedExecutionException異常,如果不設(shè)置處理策略默認(rèn)是這個(gè)方法進(jìn)行處理
**(2).ThreadPoolExecutor.CallerRunsPolicy **
直接在 execute 方法的調(diào)用線程中運(yùn)行被拒絕的任務(wù);如果執(zhí)行程序已關(guān)閉,則會(huì)丟棄該任務(wù).
**(3).ThreadPoolExecutor.DiscardOldestPolicy **
放棄最舊的未處理請求,然后重試 execute;如果執(zhí)行程序已關(guān)閉,則會(huì)丟棄該任務(wù)。
**(4).ThreadPoolExecutor.DiscardPolicy **
默認(rèn)情況下直接丟棄被拒絕的任務(wù)。
在平常使用時(shí)可以根據(jù)實(shí)際需要制定相關(guān)的操作。
小結(jié)
THreadPoolExecutor在執(zhí)行任務(wù)時(shí)大體是這樣的,如果線程池中線程的數(shù)量沒有達(dá)到核心線程的數(shù)量,它就會(huì)啟動(dòng)一個(gè)核心線程來執(zhí)行任務(wù),如果達(dá)到或超過核心線程數(shù)量,那么任務(wù)會(huì)插入到任務(wù)隊(duì)列中等待執(zhí)行,如果任務(wù)隊(duì)列滿了。但是線程數(shù)量沒有達(dá)到線程池的最大數(shù),那就回啟動(dòng)一個(gè)非核心線程(臨時(shí)工)來執(zhí)行任務(wù),如果線程數(shù)量達(dá)到線程池最大數(shù),那么就會(huì)拒絕處理新任務(wù)并調(diào)用handler的rejectedExecution來處理拒絕任務(wù)(處理策略就是上面四種)。
線程池設(shè)計(jì)者提供的四種線程池策略
線程池設(shè)計(jì)者為我們提供了四種不同的線程池策略,分別是FixedThreadPool,SingleThreadExecutor,CachedThreadPool,ScheduledThreadPool,他們放在了Executors這個(gè)類中,下面逐一分析
(1).FixedThreadPool
這個(gè)策略源碼為:
public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(),
threadFactory);
}
通過Executors.newFixedThreadPool()這個(gè)方法我們可以創(chuàng)建這個(gè)線程池,這是一個(gè)線程數(shù)量固定且可重用的線程池它的核心線程數(shù)和最大線程數(shù)是一樣的,當(dāng)線程處于空閑狀態(tài)時(shí),除非線程池關(guān)閉,否則線程不會(huì)被回收,,當(dāng)所有線程都處于活動(dòng)狀態(tài)時(shí),新任務(wù)都會(huì)處于等待狀態(tài),這個(gè)線程池策略沒有超時(shí)策略(0L),任務(wù)隊(duì)列也是無界隊(duì)列(LinkedBlockingQueue),線程工廠由調(diào)用者自己指定,拒絕任務(wù)處理策略是默認(rèn)處理策略(直接拋出RejectedExecutionException異常)。
(2).SingleThreadExecutor
這個(gè)策略源碼為:
public static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory) {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(),
threadFactory));
}
從返回的參數(shù)可以看出這個(gè)策略只有一個(gè)核心線程,它確保了所有任務(wù)都是在同一個(gè)線程中按順序來執(zhí)行,這就不需要處理線程同步的問題了,由于最大線程數(shù)跟核心線程數(shù)都是1,所以也就不存在非核心線程超時(shí)策略了,任務(wù)隊(duì)列為無界隊(duì)列,線程池工廠是默認(rèn)的線程池工廠。
(3).CachedThreadPool
這個(gè)策略源碼為:
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
從返回的參數(shù)可以看出,它并沒有核心線程數(shù)(0),最大線程數(shù)為Integer.MAX_VALUE(可以看做任意大),當(dāng)線程池的線程都處于活動(dòng)狀態(tài)時(shí),如果有新任務(wù),線程池會(huì)創(chuàng)建新的線程來處理新任務(wù),否則就會(huì)用閑置線程來處理任務(wù),它的非核心線程超時(shí)策略是60秒(60L),超過60秒閑置線程就會(huì)被回收,前面說過SynchronousQueue是一個(gè)空集合,是無法存儲(chǔ)元素的,這就造成了只要有任務(wù)就會(huì)被立即執(zhí)行,這種線程池策略審核執(zhí)行大量單耗時(shí)較少的任務(wù),如果線程池處于閑置狀態(tài),由于有超時(shí)策略,線程池中的所有線程都會(huì)被停止,這時(shí)線程池中沒有任何線程,所以不占用任何系統(tǒng)資源。
(4).ScheduledThreadPool
這個(gè)策略源碼為:
public static ScheduledExecutorService newSingleThreadScheduledExecutor() {
return new DelegatedScheduledExecutorService
(new ScheduledThreadPoolExecutor(1));
}
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE,
DEFAULT_KEEPALIVE_MILLIS, MILLISECONDS,
new DelayedWorkQueue());
}
從參數(shù)可以看出,它核心線程是調(diào)用者自己設(shè)定的,也就是一個(gè)固定的值,但是最大線程數(shù)是無限大,而且超時(shí)策略是10毫秒(基本就是立即了..)回收,而阻塞隊(duì)列是延時(shí)隊(duì)列,這也說明了這種線程池策略是執(zhí)行定時(shí)或周期重復(fù)的任務(wù)。
小結(jié)
其實(shí)當(dāng)我們前面把ThreadPoolExecutor構(gòu)造方法進(jìn)行分析以后理解Doug Lea(線程池設(shè)計(jì)者)給我們提供的幾種線程池策略就不難了,我個(gè)人覺得Doug Lea之所以給我們提供了幾種策略是為了方便我們使用線程池,但是如果我們了解了參數(shù)的含義我們完全可以自己定義一個(gè)屬于自己的線程池策略。下面我封裝一個(gè)自定義的線程池策略。
封裝自己的線程池策略
前面說了Doug Lea(線程池設(shè)計(jì)者)提供了四種具有不同特性的線程池策略給我們使用,日常開發(fā)使用其中一種也是可以的,但是我們既然懂得了原理當(dāng)然可以自己自定義一個(gè)來使用,下面我一步步封裝一個(gè)線程池策略然后進(jìn)行測試(這個(gè)策略某些參數(shù)參考了AsyncTask)。
首先是線程池代理類,源碼為:
public class ThreadPoolProxy {
private ThreadPoolExecutor executor;
private int corePollSize;//核心線程數(shù)
private int maximumPoolSize;//最大線程數(shù)
private long keepAliveTime;//非核心線程持續(xù)時(shí)間
public ThreadPoolProxy(int corePollSize, int maximumPoolSize, long keepAliveTime) {
this.corePollSize = corePollSize;
this.maximumPoolSize = maximumPoolSize;
this.keepAliveTime = keepAliveTime;
}
/**
* 單例,產(chǎn)生線程
*/
@TargetApi(Build.VERSION_CODES.GINGERBREAD)
public void getThreadPoolProxyInstant() {
if (executor == null)
synchronized (ThreadPoolProxy.class) {
if (executor == null) {
BlockingDeque<Runnable> workQueue = new LinkedBlockingDeque<Runnable>(128);//任務(wù)隊(duì)列容量128
ThreadFactory threadFactory = Executors.defaultThreadFactory();//默認(rèn)的線程工廠
RejectedExecutionHandler handler = new ThreadPoolExecutor.AbortPolicy();//拒絕任務(wù)時(shí)的處理策略,直接拋異常
executor = new ThreadPoolExecutor(corePollSize,
maximumPoolSize,
keepAliveTime,
TimeUnit.SECONDS,
workQueue,
threadFactory,
handler);
}
}
}
/**
* 執(zhí)行任務(wù)
*
* @param task
*/
public void excute(Runnable task) {
getThreadPoolProxyInstant();
executor.execute(task);
}
/**
* 刪除任務(wù)
*
* @param task
*/
public boolean remove(Runnable task) {
getThreadPoolProxyInstant();
boolean remove = executor.remove(task);
return remove;
}
}
這里我把非核心線程超時(shí)策略單位設(shè)置為了秒,任務(wù)隊(duì)列容量設(shè)置為128,線程池工廠設(shè)置成了默認(rèn)工廠,拒絕任務(wù)的策略設(shè)置成了直接拋出異常。然后剩下的三個(gè)參數(shù)在工廠類生產(chǎn)線程時(shí)進(jìn)行設(shè)置,工廠類代碼為:
public class ThreadPoolFactory {
private static ThreadPoolProxy commonThreadPool;//獲取一個(gè)普通的線程池實(shí)例
private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();//獲得CPU數(shù)量
private static final int COMMOM_CORE_POOL_SIZE = CPU_COUNT + 1;//根據(jù)CPU核數(shù)來設(shè)定核心線程數(shù)
private static final int COMMON_MAXPOOL_SIZE = CPU_COUNT * 2 + 1;//最大線程池?cái)?shù)
private static final long COMMON_KEEP_LIVE_TIME = 1L;//持續(xù)時(shí)間
public static ThreadPoolProxy getCommonThreadPool() {
if (commonThreadPool == null)
synchronized (ThreadPoolFactory.class) {
if (commonThreadPool == null)
commonThreadPool = new ThreadPoolProxy(COMMOM_CORE_POOL_SIZE,
COMMON_MAXPOOL_SIZE, COMMON_KEEP_LIVE_TIME);
}
return commonThreadPool;
}
}
這里的核心線程跟跟最大線程數(shù)是根據(jù)手機(jī)的cpu核心數(shù)進(jìn)行動(dòng)態(tài)設(shè)置,然后把使用方法封裝成一個(gè)Util類:
public class ThreadPoolUtil {
/**
* 在非UI線程中執(zhí)行
*
* @param task
*/
public static void runTaskInThread(Runnable task) {
ThreadPoolFactory.getCommonThreadPool().excute(task);
}
public static Handler handler = new Handler();
/**
* 在UI線程中執(zhí)行
*
* @param task
*/
public static void runTaskInUIThread(Runnable task) {
handler.post(task);
}
/**
* 移除線程池中線程
*
* @param task
*/
public static boolean removeTask(Runnable task) {
boolean remove = ThreadPoolFactory.getCommonThreadPool().remove(task);
return remove;
}
}
通過上面的封裝就可以自定義一個(gè)自己的線程池策略了,當(dāng)然這個(gè)策略的參數(shù)可以根據(jù)實(shí)際需要進(jìn)行配置,我這里是直接參考AsyncTask來配置的。
然后做個(gè)栗子來驗(yàn)證:
我這里是模擬下載,界面截圖如下:
主界面業(yè)務(wù)邏輯為:
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
private ProgressBar pb1;
private ProgressBar pb2;
private ProgressBar pb3;
private ProgressBar pb4;
private Button btn1;
private Button btn2;
private Button btn3;
private Button btn4;
private static final int FLAGS1 = 1;
private static final int FLAGS2 = 2;
private static final int FLAGS3 = 3;
private static final int FLAGS4 = 4;
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case FLAGS1:
pb1.setProgress(msg.arg1);
break;
case FLAGS2:
pb2.setProgress(msg.arg1);
break;
case FLAGS3:
pb3.setProgress(msg.arg1);
break;
case FLAGS4:
pb4.setProgress(msg.arg1);
break;
default:
break;
}
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
init();
}
private void init() {
pb1 = (ProgressBar) findViewById(R.id.pb1);
pb2 = (ProgressBar) findViewById(R.id.pb2);
pb3 = (ProgressBar) findViewById(R.id.pb3);
pb4 = (ProgressBar) findViewById(R.id.pb4);
btn1 = (Button) findViewById(R.id.btn1);
btn2 = (Button) findViewById(R.id.btn2);
btn3 = (Button) findViewById(R.id.btn3);
btn4 = (Button) findViewById(R.id.btn4);
btn1.setOnClickListener(this);
btn2.setOnClickListener(this);
btn3.setOnClickListener(this);
btn4.setOnClickListener(this);
}
private Runnable runnable1 = new MRunnable(FLAGS1);
private Runnable runnable2 = new MRunnable(FLAGS2);
private Runnable runnable3 = new MRunnable(FLAGS3);
private Runnable runnable4 = new MRunnable(FLAGS4);
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.btn1:
updateProcessBar(runnable1);
break;
case R.id.btn2:
updateProcessBar(runnable2);
break;
case R.id.btn3:
updateProcessBar(runnable3);
break;
case R.id.btn4:
updateProcessBar(runnable4);
break;
default:
break;
}
}
private void updateProcessBar(Runnable runnable) {
ThreadPoolUtil.runTaskInThread(runnable);
}
private class MRunnable implements Runnable {
private int mFlags;
public MRunnable(int flags) {
this.mFlags = flags;
}
@Override
public void run() {
for (int i = 0; i <= 100; i++) {
Message msg = mHandler.obtainMessage();
//隨機(jī)數(shù),讓下載更真實(shí)...
Random rand = new Random();
int randNum = rand.nextInt(100);
SystemClock.sleep(randNum);
msg.what = mFlags;
msg.arg1 = i;
mHandler.sendMessage(msg);
}
}
}
/**
* 退出時(shí)清除線程
*/
@Override
protected void onDestroy() {
super.onDestroy();
ThreadPoolUtil.removeTask(runnable1);
ThreadPoolUtil.removeTask(runnable2);
ThreadPoolUtil.removeTask(runnable3);
ThreadPoolUtil.removeTask(runnable4);
}
}
然后為了方便測試,我把核心線程數(shù)固定,而不是根據(jù)CPU核心數(shù)來改變,當(dāng)我把核心線程數(shù)定為1,這時(shí)效果為:
可以看到,當(dāng)我依次啟動(dòng)1、2、3、4時(shí),由于隊(duì)列我們設(shè)置成了LinkedBlockingDeque,這個(gè)隊(duì)列是按 FIFO(先進(jìn)先出)排序元素的,所以就會(huì)按順序來執(zhí)行,也就是當(dāng)1執(zhí)行時(shí)其他任務(wù)的都在隊(duì)列中阻塞,只有1完成了才會(huì)往下執(zhí)行,這也印證了我們關(guān)于線程池的的參數(shù)屬性講解,這里可能你們會(huì)有疑問,前面不是說當(dāng)任務(wù)數(shù)超過corePoolSize就會(huì)創(chuàng)建新的非核心線程(臨時(shí)工)去處理任務(wù)嗎,這里怎么沒有這樣?其實(shí)創(chuàng)建新的線程是有條件的,這個(gè)條件是你的任務(wù)隊(duì)列已經(jīng)滿了,這時(shí)如果還加新的任務(wù)才會(huì)去創(chuàng)建非核心線程去處理這個(gè)任務(wù),要驗(yàn)證其實(shí)也很簡單,只要把
BlockingDeque<Runnable> workQueue = new LinkedBlockingDeque<Runnable>(128);
這個(gè)隊(duì)列大小從128改為2,再重新啟動(dòng),效果為:
從圖片可以看到,當(dāng)我依次啟動(dòng)任務(wù)1、2、3、4時(shí),順序是先1啟動(dòng),然后2、3阻塞,然后再啟動(dòng)4,4也跟著一起執(zhí)行,然后這兩個(gè)任務(wù)執(zhí)行完之后才依次執(zhí)行2、3,這也驗(yàn)證了我們上面的分析,我啟動(dòng)任務(wù)1,1立即執(zhí)行,這時(shí)任務(wù)隊(duì)列是沒有任務(wù)在等待的,然后我啟動(dòng)任務(wù)2、3,這時(shí)任務(wù)隊(duì)列并沒有滿,所以這兩個(gè)任務(wù)就在任務(wù)隊(duì)列中阻塞等待,當(dāng)我再啟動(dòng)任務(wù)4時(shí),這時(shí)任務(wù)隊(duì)列已經(jīng)滿了,而線程數(shù)量沒達(dá)到最大線程數(shù),所以這個(gè)策略就新開了一個(gè)非核心線程去執(zhí)行4這個(gè)新的任務(wù),然后由于這時(shí)沒有新任務(wù),所以總共就有兩個(gè)線程去執(zhí)行任務(wù)了,當(dāng)1,4有一個(gè)執(zhí)行完了,就會(huì)去隊(duì)列中依次執(zhí)行2,3。
把任務(wù)隊(duì)列大小調(diào)回128繼續(xù)我們的論證,把核心線程數(shù)改成2,這時(shí)效果為:
可以看到它每次起兩個(gè)線程去完成下載任務(wù),然后如果兩個(gè)核心線程任務(wù)沒結(jié)束后續(xù)任務(wù)會(huì)阻塞等待,只有其中一個(gè)完成了才會(huì)接著執(zhí)行。
從上面的栗子也可以看出,當(dāng)我們把核心線程修改是就可以自己定義下載的規(guī)則了,平常我們看一些Android市場下載軟件原理用的就是自己的線程池策略。
總結(jié)
Android的線程池策略對(duì)我們管理應(yīng)用中的線程是一個(gè)很方便高效的事,在日常開發(fā)中我們可以根據(jù)實(shí)際需要去配置符合業(yè)務(wù)需求的線程池策略,這樣可以讓應(yīng)用處理涉及耗時(shí)的任務(wù)時(shí)更高效和節(jié)省資源開銷,這篇文章我是很用心的寫了,在寫的過程中我自己對(duì)某些知識(shí)點(diǎn)也是有一種恍然大悟的感覺,所以我很希望這篇文章對(duì)想了解這方面知識(shí)點(diǎn)的人有所幫助,如果某些地方不了解或覺得哪個(gè)點(diǎn)錯(cuò)了,可以評(píng)論(雖然不一定及時(shí)看到..)但是有疑問我一定盡力去解答(微笑臉)。
最后雖然所有類都在文章中但是還是附個(gè)文章涉及的源碼,可能有些筒子是直接看源碼的(比如我)...
平常一直用svn管理代碼,前不久剛學(xué)會(huì)怎么使用git(感覺自己有點(diǎn)土逼的感覺),所以這次直接把源碼傳到github上,有些同學(xué)可能習(xí)慣在csdn上下載,所以多搞了個(gè)csdn鏈接,我就是這么人性化(驕傲臉)..年紀(jì)大了有些碎碎嘴了..
github地址:https://github.com/songjiran/ThreadPoolDemo
csdn地址:http://download.csdn.net/detail/lxzmmd/9535019