初探Android線程池

前言

最近在看OkHttp的源碼,看的時候發現有關線程池的運用,本身就仔細想了一下,這個塊知識好像不是很牢固。沒辦法,再研究一下有關線程池的相關知識吧。學習就是一個查漏補缺的過程,最終的目的仍是要造成本身的知識網絡。 markdown

爲何要使用線程池

平時在Android開發的過程當中常常會用到多線程異步處理相關任務,每開一個線程都要新建一個Thread對象來處理,這種操做會形成哪些後果呢?網絡

一、系統執行多任務時,會爲每一個任務建立對應的線程,當任務執行結束以後會銷燬對應的線程,在這種狀況下對象被頻繁的建立和銷燬。
二、當對線程象被頻繁時會佔用大量的系統資源,在併發的過程當中會形成資源競爭出現問題。大量的建立線程還會形成混亂,沒有一個統一的管理機制,容易形成應用卡頓。
三、大量線程對象被頻繁銷燬,將會頻繁出發GC機制,從而下降性能。
多線程

因爲多線程異步處理任務有可能形成這樣或者那樣的問題,那麼線程池應運而生。併發

線程池的做用

咱們來看一下使用線程池的好處:異步

一、重用線程池中的線程,避免因頻繁建立和銷燬線程形成的性能消耗。
二、更加有效的控制線程的最大併發數,防止線程過多搶佔資源形成的系統阻塞。
三、對線程進行有效的管理。
函數

線程池類繼承結構

咱們先看一張有關線程池的類繼承結構圖:oop

Excutor:
 這只是一個接口,其中只定義了一個execute(Runnable command)方法,從接收參數來看只能執行Runnable任務,不能執行Callable帶有的返回值的任務。性能

ExecutorService:
 這也是一個接口,繼承於Excutor。可是它Excutor的基礎上添加了管理線程池生命週期的方法shutdown()shutdownNow()。同時,ExecutorService還支執行Callable帶有返回值的任務。當提交完任務以後會拿到一個Future返回值,這個返回值表明了任務執行完畢的結果。
shutdown()會等以前的任務執行完畢以後在關閉,同時也不會再接收新任務。若是咱們須要等待線程池處理完成再返回,可使用awaitTermination方法來等待完成。
shutdownNow()方法會嘗試立刻關閉全部正在執行的任務,而且跳過全部已經提交可是尚未運行的任務。可是對於正在執行的任務,是否可以成功關閉它是沒法保證的,有可能他們真的被關閉掉了,也有可能它會一直執行到任務結束。學習

ThreadPoolExecutor:
 這個類線程池的核心類,咱們這篇文章主要來分析它。this

ThreadPoolExecutor

構造函數

這個類中有不少構造函數,咱們找一個參數最多的構造函數來看一下。

public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) {
        if (corePoolSize < 0 ||
            maximumPoolSize <= 0 ||
            maximumPoolSize < corePoolSize ||
            keepAliveTime < 0)
            throw new IllegalArgumentException();
        if (workQueue == null || threadFactory == null || handler == null)
            throw new NullPointerException();
        this.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
    }
複製代碼

corePoolSize

這個參數表示的是核型線程數,當一個請求進來時當前線程池中的線程個數小於核心線程數,能夠直接經過ThreadFactory建立線程池;若是已經大於核心線程數時,則將任務放入到workQueue(任務隊列)中。

maximumPoolSize

這個參數表示線程池中能夠建立的最大線程數。當線程池中的線程數等於corePoolSize而且workQueue(任務隊列)已滿,這時就要看當前線程數是否大於maximumPoolSize,若是小於則會建立線程去執行任務,不然會時候「飽和策略」去拒絕這個任務請求。對於超過corePoolSize的線程稱之爲「idle Thread」,這部分線程會有一個最大的空閒存活時間,若是超過這個空閒存活時間尚未任務被分配,則會將這些線程進行回收。

keepAliveTime 和 unit

這兩個參數就是用來控制「idle Thread」(空閒線程)的空閒存活時間,u nit表示時間單位,當超過這個時間時將會被回收。在ThreadPoolExecutor中有一個很是重要參數private volatile boolean allowCoreThreadTimeOut,這個參數表示當核心線程超過最大空閒時間還沒被分配任務是是否回收,默認返回false,表示不會被回收。若是將這個參數設置成true的話,當核心線程超過最大空閒時間時將會被回收。

workQueue

阻塞隊列,當線程數超過corePoolSize的部分任務會被放入到這個隊列中等待執行。阻塞隊列也會被分紅有界和無界,當咱們制定這個隊列的capacity時,就是一個有界阻塞隊列,反之就是一個無界阻塞隊列。當該隊列爲無界阻塞隊列時,會有大量的任務被存入,從而致使內存溢出系統崩潰。

threadFactory

這是一個線程工廠,被用來爲線程池建立線程。當咱們不指定線程工廠時,線程池內部會調用Executors.defaultThreadFactory()建立默認的線程工廠,其後續建立的線程優先級都是Thread.NORM_PRIORITY。若是咱們指定線程工廠,咱們能夠對產生的線程進行必定的操做。

handler

飽和策略,當線程池達到飽和狀態時拒絕多餘的任務。ThreadPoolExecutor中有三種飽和策略,AbortPolicy:執行策略時拋出RejectedExecutionException異常。CallerRunsPolicy:不在線程池中運行任務,在調用者的線程中運行任務。DiscardOldestPolicy:將隊列中等待最久的直從隊列頭部移除,將新的任務加入到隊列尾部。DiscardPolicy:直接丟棄任務。

執行方法

線程池中有兩種執行方法,分別是submit()execute(),下面咱們經過源碼看一下二者的區別。

execute()

public void execute(Runnable command) {
        if (command == null)
            throw new NullPointerException();
        int c = ctl.get(); //1
        if (workerCountOf(c) < corePoolSize) { //2
            if (addWorker(command, true)) //3
                return;
            c = ctl.get();
        }
        if (isRunning(c) && workQueue.offer(command)) { //4
            int recheck = ctl.get();
            if (! isRunning(recheck) && remove(command)) //5
                reject(command);
            else if (workerCountOf(recheck) == 0) //6
                addWorker(null, false);
        }
        else if (!addWorker(command, false)) //7
            reject(command);
    }
複製代碼

在上面有幾處註釋,咱們看一下。

一、獲取當前線程池的狀態和有效線程的個數。
二、判斷當前線程池中的線程個數是否小於核心線程數。
三、如若沒有超過核心線程數,將直接建立核心線程執行任務,若是建立成功直接返回,若是不成功將進行下一步
四、判斷當前線程池的狀態是否爲RUNNING狀態,並將任務添加到隊列中。
五、查看一下線程池的狀態,若是不是RUNNING,直接移除。
六、若是當前線程池中線程數量爲0,則單首創建線程,可是不指定任務。
七、若是上述條件都不瞞住,而且建立一個非核心線程來執行任務失敗,直接調用reject方法

submit()

/** * @throws RejectedExecutionException {@inheritDoc} * @throws NullPointerException {@inheritDoc} */
    public Future<?> submit(Runnable task) {
        if (task == null) throw new NullPointerException();
        RunnableFuture<Void> ftask = newTaskFor(task, null);
        execute(ftask);
        return ftask;
    }

/** * @throws RejectedExecutionException {@inheritDoc} * @throws NullPointerException {@inheritDoc} */
    public <T> Future<T> submit(Runnable task, T result) {
        if (task == null) throw new NullPointerException();
        RunnableFuture<T> ftask = newTaskFor(task, result);
        execute(ftask);
        return ftask;
    }
    
/** * @throws RejectedExecutionException {@inheritDoc} * @throws NullPointerException {@inheritDoc} */
    public <T> Future<T> submit(Callable<T> task) {
        if (task == null) throw new NullPointerException();
        RunnableFuture<T> ftask = newTaskFor(task);
        execute(ftask);
        return ftask;
    }
複製代碼

submit(...)方法實際上是AbstractExecutorService中的方法,ThreadPoolExecutor繼承於它,這裏的submit``方法又會調用ThreadPoolExecutorexecute(...)```方法。

線程的池的運行過程

從上面的執行方法咱們能得到下面關於線程池運行過程的圖。

線程池的運行過程主要分一下幾個步驟:

一、當須要執行的任務被提交到線程池後,首先判斷當前運行的線程是否少於corePoolSize。若是小於,則建立新線程來執行任務。
二、若是當前線程池中運行的線程等於或多於corePoolSize,則將任務加入BlockingQueue
三、BlockingQueue已滿則沒法將任務加入,這時就會建立新的線程來處理任務。
四、若是建立新線程會讓當前運行的線程數超出maximumPoolSize,拒絕任務,並調用RejectedExecutionHandler.rejectedExecution()方法。

文章開頭也講過,最近在看okhttp源碼的時候碰到線程池這個不太熟悉的知識點,就趕忙過來研究一下。因爲時間倉促,這篇文章有些簡短且淺顯,有關線程池深刻的知識將會在後續文章中展現。

參考資料

線程池原理
Java線程池

相關文章
相關標籤/搜索