Android中線程池的使用

線程池算是Android開發(fā)中非常常用的一個東西了,只要涉及到線程的地方,大多數(shù)情況下都會涉及到線程池。Android開發(fā)中線程池的使用和Java中線程池的使用基本一致。那么今天我想來總結(jié)一下Android開發(fā)中線程池的使用。
假如說要做一個ListView,ListView上有一個item,每個item上都有一張圖片需要從網(wǎng)絡(luò)上加載,如果不使用線程池,簡單的通過下面的方式來開啟一個新線程:

new Thread(new Runnable() {
            @Override
            public void run() {
                //網(wǎng)絡(luò)訪問
            }
        }).start();

這種用法主要存在以下3點(diǎn)問題:

1.針對每一個item都創(chuàng)建一個新線程,這樣會導(dǎo)致頻繁的創(chuàng)建線程,線程執(zhí)行完之后又被回收,又會導(dǎo)致頻繁的GC
2.這么多線程缺乏統(tǒng)一管理,各線程之間互相競爭,降低程序的運(yùn)行效率,手機(jī)頁面卡頓,甚至?xí)?dǎo)致程序崩潰
3.如果一個item滑出頁面,則要停止該item上圖片的加載,但是如果使用這種方式來創(chuàng)建線程,則無法實現(xiàn)線程停止執(zhí)行

如果使用線程池,我們就可以很好的解決以上三個問題:

1.重用已經(jīng)創(chuàng)建的好的線程,避免頻繁創(chuàng)建進(jìn)而導(dǎo)致的頻繁GC
2.控制線程并發(fā)數(shù),合理使用系統(tǒng)資源,提高應(yīng)用性能
3.可以有效的控制線程的執(zhí)行,比如定時執(zhí)行,取消執(zhí)行等

Android中的線程池其實源于Java,都是通過對ThreadPoolExecutor進(jìn)行不同配置來實現(xiàn)的,那么我們今天就從這這個ThreadPoolExecutor來開始吧!

ThreadPoolExecutor有四個重載的構(gòu)造方法,我們這里來說說參數(shù)最多的那一個重載的構(gòu)造方法,這樣大家就知道其他方法參數(shù)的含義了,如下:

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) 

corePoolSize: 線程池中核心線程的數(shù)量
maximumPoolSize : 線程池中最大線程數(shù)量
keepAliveTime: 非核心線程的超時時長,當(dāng)系統(tǒng)中非核心線程閑置時間超過keepAliveTime之后,則會被回收。如果ThreadPoolExecutor的allowCoreThreadTimeOut屬性設(shè)置為true,則該參數(shù)也表示核心線程的超時時長
unit: 第三個參數(shù)的單位,有納秒、微秒、毫秒、秒、分、時、天等
workQueue: 線程池中的任務(wù)隊列,該隊列主要用來存儲已經(jīng)被提交但是尚未執(zhí)行的任務(wù)。存儲在這里的任務(wù)是由ThreadPoolExecutor的execute方法提交來的。
threadFactory: 為線程池提供創(chuàng)建新線程的功能,這個我們一般使用默認(rèn)即可
handler: 拒絕策略,當(dāng)線程無法執(zhí)行新任務(wù)時(一般是由于線程池中的線程數(shù)量已經(jīng)達(dá)到最大數(shù)或者線程池關(guān)閉導(dǎo)致的),默認(rèn)情況下,當(dāng)線程池?zé)o法處理新線程時,會拋出一個RejectedExecutionException。

附:

TimeUnit.DAYS;               //天
TimeUnit.HOURS;             //小時
TimeUnit.MINUTES;           //分鐘
TimeUnit.SECONDS;           //秒
TimeUnit.MILLISECONDS;      //毫秒
TimeUnit.MICROSECONDS;      //微妙
TimeUnit.NANOSECONDS;       //納秒

除此之外,還要說的是workQueue是一個BlockingQueue類型,而BlockingQueue是一個特殊的隊列,當(dāng)我們從BlockingQueue中取數(shù)據(jù)時,如果BlockingQueue是空的,則取數(shù)據(jù)的操作會進(jìn)入到阻塞狀態(tài),當(dāng)BlockingQueue中有了新數(shù)據(jù)時,這個取數(shù)據(jù)的操作又會被重新喚醒。同理,如果BlockingQueue中的數(shù)據(jù)已經(jīng)滿了,往BlockingQueue中存數(shù)據(jù)的操作又會進(jìn)入阻塞狀態(tài),直到BlockingQueue中又有新的空間,存數(shù)據(jù)的操作又會被沖洗喚醒。

下面說說BlockingQueue的幾個常用的實現(xiàn)類:

1.ArrayBlockingQueue:這個表示一個規(guī)定了大小的BlockingQueue,ArrayBlockingQueue的構(gòu)造函數(shù)接受一個int類型的數(shù)據(jù),該數(shù)據(jù)表示BlockingQueue的大小,存儲在ArrayBlockingQueue中的元素按照FIFO(先進(jìn)先出)的方式來進(jìn)行存取。
2.LinkedBlockingQueue:這個表示一個大小不確定的BlockingQueue,在LinkedBlockingQueue的構(gòu)造方法中可以傳一個int類型的數(shù)據(jù),這樣創(chuàng)建出來的LinkedBlockingQueue是有大小的,也可以不傳,不傳的話,LinkedBlockingQueue的大小就為Integer.MAX_VALUE。
3.PriorityBlockingQueue:這個隊列和LinkedBlockingQueue類似,不同的是PriorityBlockingQueue中的元素不是按照FIFO來排序的,而是按照元素的Comparator來決定存取順序的(這個功能也反映了存入PriorityBlockingQueue中的數(shù)據(jù)必須實現(xiàn)了Comparator接口)。
4.SynchronousQueue:這個是同步Queue,屬于線程安全的BlockingQueue的一種,在SynchronousQueue中,生產(chǎn)者線程的插入操作必須要等待消費(fèi)者線程的移除操作,Synchronous內(nèi)部沒有數(shù)據(jù)緩存空間,因此我們無法對SynchronousQueue進(jìn)行讀取或者遍歷其中的數(shù)據(jù),元素只有在你試圖取走的時候才有可能存在。

線程池工作流程:

1.corePoolSize 也稱為工作線程,沒有任務(wù)到來之前就創(chuàng)建corePoolSize個線程。當(dāng)一個任務(wù)到來的時候,如果任務(wù)隊列為空,工作線程也有空閑,就啟用一個工作線程處理任務(wù)。
2.如果工作線程都處于運(yùn)行的狀態(tài),而任務(wù)隊列workQueue還沒有滿,則往隊列中放入線程信息。
3.如果工作線程都處于運(yùn)行狀態(tài),任務(wù)隊列又滿了,maximumPoolSize大于corePoolSize。就會新建一個非核心進(jìn)程處理新到來的任務(wù)。
4.如果非核心進(jìn)程在keepAliveTime時間內(nèi)都處于空閑狀態(tài)就會被回收掉。
5.或者如果線程池中的線程數(shù)已經(jīng)超過非核心線程數(shù),則拒絕執(zhí)行該任務(wù)。

我們在實際開發(fā)中如果需要自己來配置這些參數(shù),該如何配置呢?參考一下AsyncTask,AsyncTask部分源碼如下:

public abstract class AsyncTask<Params, Progress, Result> {
    private static final String LOG_TAG = "AsyncTask";

    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);
        ....
        ....
}

核心線程數(shù)為手機(jī)CPU數(shù)量+1(cpu數(shù)量獲取方式Runtime.getRuntime().availableProcessors()),最大線程數(shù)為手機(jī)CPU數(shù)量×2+1,線程隊列的大小為128,OK,那么小伙伴們在以后使用線程池的過程中可以參考這個再結(jié)合自己的實際情況來配置參數(shù)。

接下來我們就來看看一下四種系統(tǒng)配置好的提供給我們的線程池:

1.FixedThreadPool

FixedThreadPool是一個核心線程和最大線程數(shù)量相等的的線程池,因此線程池中的線程數(shù)目是一定的,創(chuàng)建方式如下:

ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);

且線程的超時時間為0,說明核心線程即使在沒有任務(wù)可執(zhí)行的時候也不會被銷毀

2.SingleThreadExecutor

singleThreadExecutor和FixedThreadPool很像,不同的就是SingleThreadExecutor的核心線程數(shù)只有1。

ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
3.CachedThreadPool

CachedTreadPool一個最大的優(yōu)勢是它可以根據(jù)程序的運(yùn)行情況自動來調(diào)整線程池中的線程數(shù)量。

ExecutorService cachedThreadPool = Executors.newCachedThreadPool();

CachedThreadPool中是沒有核心線程的,但是它的最大線程數(shù)卻為Integer.MAX_VALUE,另外,它是有線程超時機(jī)制的,超時時間為60秒,這里它使用了SynchronousQueue作為線程隊列,SynchronousQueue的特點(diǎn)上文已經(jīng)說過了,這里不再贅述。由于最大線程數(shù)為無限大,所以每當(dāng)我們添加一個新任務(wù)進(jìn)來的時候,如果線程池中有空閑的線程,則由該空閑的線程執(zhí)行新任務(wù),如果沒有空閑線程,則創(chuàng)建新線程來執(zhí)行任務(wù)。根據(jù)CachedThreadPool的特點(diǎn),我們可以在有大量任務(wù)請求的時候使用CachedThreadPool,因為當(dāng)CachedThreadPool中沒有新任務(wù)的時候,它里邊所有的線程都會因為超時而被終止。

4.ScheduledThreadPool

ScheduledThreadPool是一個具有定時定期執(zhí)行任務(wù)功能的線程池。

ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(3);

它的核心線程數(shù)量是固定的(我們在構(gòu)造的時候傳入的),但是非核心線程是無窮大,當(dāng)非核心線程閑置時,則會被立即回收。
它擁有幾種特殊的啟動任務(wù)的方式:
1.延遲啟動任務(wù):

 scheduledExecutorService.schedule(runnable, 1, TimeUnit.SECONDS);

延遲1秒啟動任務(wù)。
2.延遲定時執(zhí)行任務(wù):

scheduledExecutorService.scheduleAtFixedRate(runnable, 1, 1, TimeUnit.SECONDS);

延遲1秒之后每隔1秒執(zhí)行一次新任務(wù)。
3.延遲執(zhí)行任務(wù)

scheduledExecutorService.scheduleWithFixedDelay(runnable, 1, 1, TimeUnit.SECONDS);

第一次延遲1秒之后,以后每次也延遲1秒執(zhí)行。

scheduleAtFixedRate周期性的,不管當(dāng)前任務(wù)執(zhí)行完沒有,到了時間就執(zhí)行下一個任務(wù)。
scheduleWithFixedDelay,必須當(dāng)前任務(wù)執(zhí)行完畢,然后延遲所設(shè)置的時間就執(zhí)行下一個任務(wù)。

線程池的常用功能:

1.shutDown() 關(guān)閉線程池,停止接收新任務(wù),執(zhí)行完線程池中的任務(wù)和任務(wù)隊列中的任務(wù)才關(guān)閉線程池
2.shutDownNow() 關(guān)閉線程池,停止接收新任務(wù),并嘗試去終止正在執(zhí)行的線程
3.allowCoreThreadTimeOut(boolean value) 允許核心線程閑置超時時被回收
4.submit 一般情況下我們使用execute來提交任務(wù),但是有時候可能也會用到submit,使用submit的好處是submit有返回值,submit通常與callable線程相聯(lián)系。

Android ListView異步加載圖片亂序問題,原因分析及解決方案:可以使用AsynTask進(jìn)行網(wǎng)絡(luò)圖片的下載,而AsynTask的原理就是線程池。所有提交的異步任務(wù)都會在這個線程池中的工作線程內(nèi)執(zhí)行,當(dāng)工作線程需要跟UI線程交互時,工作線程會通過向在UI線程創(chuàng)建的Handler。傳遞消息的方式,調(diào)用相關(guān)的回調(diào)函數(shù),從而實現(xiàn)UI界面的更新。

參考博客:http://m.blog.csdn.net/u012702547/article/details/52259529

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容